webarmor 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.
- package/dist/index.cjs +2199 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +281 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.js +2162 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/modules/ddos/DDoSProtection.ts
|
|
34
|
+
var DDoSProtection_exports = {};
|
|
35
|
+
__export(DDoSProtection_exports, {
|
|
36
|
+
DDoSProtection: () => DDoSProtection,
|
|
37
|
+
default: () => DDoSProtection_default
|
|
38
|
+
});
|
|
39
|
+
var DDoSProtection, DDoSProtection_default;
|
|
40
|
+
var init_DDoSProtection = __esm({
|
|
41
|
+
"src/modules/ddos/DDoSProtection.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
DDoSProtection = class {
|
|
44
|
+
config;
|
|
45
|
+
logger;
|
|
46
|
+
ipMap = /* @__PURE__ */ new Map();
|
|
47
|
+
cleanupInterval = null;
|
|
48
|
+
constructor(config, logger2) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.logger = logger2;
|
|
51
|
+
this.startCleanup();
|
|
52
|
+
}
|
|
53
|
+
startCleanup() {
|
|
54
|
+
this.cleanupInterval = setInterval(() => {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const [ip, data] of this.ipMap.entries()) {
|
|
57
|
+
if (data.blocked && data.blockUntil && now > data.blockUntil) {
|
|
58
|
+
data.blocked = false;
|
|
59
|
+
data.blockUntil = void 0;
|
|
60
|
+
this.logger.info(`IP ${ip} unblocked after DDoS protection`);
|
|
61
|
+
}
|
|
62
|
+
if (now - data.lastRequest > this.config.windowMs * 2) {
|
|
63
|
+
this.ipMap.delete(ip);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, 6e4);
|
|
67
|
+
}
|
|
68
|
+
async check(data) {
|
|
69
|
+
const ip = data.ip || "unknown";
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
let ipData = this.ipMap.get(ip);
|
|
72
|
+
if (!ipData) {
|
|
73
|
+
ipData = {
|
|
74
|
+
count: 0,
|
|
75
|
+
firstRequest: now,
|
|
76
|
+
lastRequest: now,
|
|
77
|
+
blocked: false,
|
|
78
|
+
score: 0
|
|
79
|
+
};
|
|
80
|
+
this.ipMap.set(ip, ipData);
|
|
81
|
+
}
|
|
82
|
+
if (ipData.blocked && ipData.blockUntil && now < ipData.blockUntil) {
|
|
83
|
+
return {
|
|
84
|
+
allowed: false,
|
|
85
|
+
reason: `DDoS protection: IP temporarily blocked until ${new Date(ipData.blockUntil).toISOString()}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
ipData.count++;
|
|
89
|
+
ipData.lastRequest = now;
|
|
90
|
+
const windowStart = now - this.config.windowMs;
|
|
91
|
+
const requestsInWindow = ipData.count;
|
|
92
|
+
if (requestsInWindow > this.config.maxRequests) {
|
|
93
|
+
ipData.blocked = true;
|
|
94
|
+
ipData.blockUntil = now + this.config.blockDurationMs;
|
|
95
|
+
ipData.score += 10;
|
|
96
|
+
this.logger.warn(`DDoS detected from IP ${ip}`, {
|
|
97
|
+
requestsInWindow,
|
|
98
|
+
maxRequests: this.config.maxRequests,
|
|
99
|
+
blocked: true
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
allowed: false,
|
|
103
|
+
reason: "DDoS protection: Too many requests"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
ipData.score = Math.max(0, ipData.score - 1);
|
|
107
|
+
if (ipData.score > this.config.scoreThreshold) {
|
|
108
|
+
ipData.blocked = true;
|
|
109
|
+
ipData.blockUntil = now + this.config.blockDurationMs;
|
|
110
|
+
this.logger.warn(`High risk score for IP ${ip}`, {
|
|
111
|
+
score: ipData.score,
|
|
112
|
+
threshold: this.config.scoreThreshold
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
allowed: false,
|
|
116
|
+
reason: "DDoS protection: High risk score"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { allowed: true };
|
|
120
|
+
}
|
|
121
|
+
getStats() {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
let active = 0;
|
|
124
|
+
let blocked = 0;
|
|
125
|
+
for (const data of this.ipMap.values()) {
|
|
126
|
+
if (!data.blocked || data.blockUntil && now < data.blockUntil) {
|
|
127
|
+
active++;
|
|
128
|
+
}
|
|
129
|
+
if (data.blocked) {
|
|
130
|
+
blocked++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
totalTracked: this.ipMap.size,
|
|
135
|
+
activeConnections: active,
|
|
136
|
+
blockedIPs: blocked
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
middleware() {
|
|
140
|
+
return async (req, res, next) => {
|
|
141
|
+
const ip = req.ip || req.connection?.remoteAddress || req.headers?.["x-forwarded-for"] || "unknown";
|
|
142
|
+
const data = {
|
|
143
|
+
ip,
|
|
144
|
+
method: req.method,
|
|
145
|
+
url: req.url,
|
|
146
|
+
headers: req.headers,
|
|
147
|
+
body: req.body,
|
|
148
|
+
userAgent: req.headers?.["user-agent"]
|
|
149
|
+
};
|
|
150
|
+
const result = await this.check(data);
|
|
151
|
+
if (!result.allowed) {
|
|
152
|
+
return res.status(403).json({
|
|
153
|
+
error: "Forbidden",
|
|
154
|
+
message: result.reason
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
next();
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
destroy() {
|
|
161
|
+
if (this.cleanupInterval) {
|
|
162
|
+
clearInterval(this.cleanupInterval);
|
|
163
|
+
}
|
|
164
|
+
this.ipMap.clear();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
DDoSProtection_default = DDoSProtection;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// src/modules/ratelimit/RateLimiter.ts
|
|
172
|
+
var RateLimiter_exports = {};
|
|
173
|
+
__export(RateLimiter_exports, {
|
|
174
|
+
RateLimiter: () => RateLimiter,
|
|
175
|
+
default: () => RateLimiter_default
|
|
176
|
+
});
|
|
177
|
+
var RateLimiter, RateLimiter_default;
|
|
178
|
+
var init_RateLimiter = __esm({
|
|
179
|
+
"src/modules/ratelimit/RateLimiter.ts"() {
|
|
180
|
+
"use strict";
|
|
181
|
+
RateLimiter = class {
|
|
182
|
+
config;
|
|
183
|
+
logger;
|
|
184
|
+
limits = /* @__PURE__ */ new Map();
|
|
185
|
+
refillInterval = null;
|
|
186
|
+
constructor(config, logger2) {
|
|
187
|
+
this.config = config;
|
|
188
|
+
this.logger = logger2;
|
|
189
|
+
this.startRefill();
|
|
190
|
+
}
|
|
191
|
+
startRefill() {
|
|
192
|
+
this.refillInterval = setInterval(() => {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
for (const [key, data] of this.limits.entries()) {
|
|
195
|
+
const refillAmount = (now - data.lastRefill) / this.config.windowMs * this.config.max;
|
|
196
|
+
data.tokens = Math.min(this.config.max, data.tokens + refillAmount);
|
|
197
|
+
data.lastRefill = now;
|
|
198
|
+
if (data.tokens >= this.config.max && now - data.lastRefill > this.config.windowMs * 2) {
|
|
199
|
+
this.limits.delete(key);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}, 1e3);
|
|
203
|
+
}
|
|
204
|
+
async check(key) {
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
let data = this.limits.get(key);
|
|
207
|
+
if (!data) {
|
|
208
|
+
data = {
|
|
209
|
+
tokens: this.config.max - 1,
|
|
210
|
+
lastRefill: now
|
|
211
|
+
};
|
|
212
|
+
this.limits.set(key, data);
|
|
213
|
+
return { allowed: true, remaining: data.tokens };
|
|
214
|
+
}
|
|
215
|
+
const refillAmount = (now - data.lastRefill) / this.config.windowMs * this.config.max;
|
|
216
|
+
data.tokens = Math.min(this.config.max, data.tokens + refillAmount);
|
|
217
|
+
data.lastRefill = now;
|
|
218
|
+
if (data.tokens < 1) {
|
|
219
|
+
this.logger.warn(`Rate limit exceeded for key: ${key}`);
|
|
220
|
+
return {
|
|
221
|
+
allowed: false,
|
|
222
|
+
reason: this.config.message || "Too many requests",
|
|
223
|
+
remaining: 0
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
data.tokens -= 1;
|
|
227
|
+
return { allowed: true, remaining: Math.floor(data.tokens) };
|
|
228
|
+
}
|
|
229
|
+
middleware() {
|
|
230
|
+
return async (req, res, next) => {
|
|
231
|
+
const key = this.config.keyGenerator ? this.config.keyGenerator(req) : req.ip || req.connection?.remoteAddress || "unknown";
|
|
232
|
+
const result = await this.check(key);
|
|
233
|
+
res.setHeader("X-RateLimit-Limit", this.config.max);
|
|
234
|
+
res.setHeader("X-RateLimit-Remaining", result.remaining ?? 0);
|
|
235
|
+
if (!result.allowed) {
|
|
236
|
+
return res.status(this.config.statusCode || 429).json({
|
|
237
|
+
error: "Too Many Requests",
|
|
238
|
+
message: result.reason
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
next();
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
getStats() {
|
|
245
|
+
return {
|
|
246
|
+
activeKeys: this.limits.size
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
reset(key) {
|
|
250
|
+
if (key) {
|
|
251
|
+
this.limits.delete(key);
|
|
252
|
+
} else {
|
|
253
|
+
this.limits.clear();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
destroy() {
|
|
257
|
+
if (this.refillInterval) {
|
|
258
|
+
clearInterval(this.refillInterval);
|
|
259
|
+
}
|
|
260
|
+
this.limits.clear();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
RateLimiter_default = RateLimiter;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// src/modules/security-headers/SecurityHeaders.ts
|
|
268
|
+
var SecurityHeaders_exports = {};
|
|
269
|
+
__export(SecurityHeaders_exports, {
|
|
270
|
+
SecurityHeaders: () => SecurityHeaders,
|
|
271
|
+
default: () => SecurityHeaders_default
|
|
272
|
+
});
|
|
273
|
+
var SecurityHeaders, SecurityHeaders_default;
|
|
274
|
+
var init_SecurityHeaders = __esm({
|
|
275
|
+
"src/modules/security-headers/SecurityHeaders.ts"() {
|
|
276
|
+
"use strict";
|
|
277
|
+
SecurityHeaders = class {
|
|
278
|
+
config;
|
|
279
|
+
logger;
|
|
280
|
+
constructor(config, logger2) {
|
|
281
|
+
this.config = config;
|
|
282
|
+
this.logger = logger2;
|
|
283
|
+
}
|
|
284
|
+
buildCSP() {
|
|
285
|
+
const csp = this.config.contentSecurityPolicy;
|
|
286
|
+
if (!csp) return "";
|
|
287
|
+
const parts = [];
|
|
288
|
+
if (csp.defaultSrc) parts.push(`default-src ${csp.defaultSrc.join(" ")}`);
|
|
289
|
+
if (csp.scriptSrc) parts.push(`script-src ${csp.scriptSrc.join(" ")}`);
|
|
290
|
+
if (csp.styleSrc) parts.push(`style-src ${csp.styleSrc.join(" ")}`);
|
|
291
|
+
if (csp.imgSrc) parts.push(`img-src ${csp.imgSrc.join(" ")}`);
|
|
292
|
+
if (csp.connectSrc) parts.push(`connect-src ${csp.connectSrc.join(" ")}`);
|
|
293
|
+
if (csp.fontSrc) parts.push(`font-src ${csp.fontSrc.join(" ")}`);
|
|
294
|
+
if (csp.objectSrc) parts.push(`object-src ${csp.objectSrc.join(" ")}`);
|
|
295
|
+
if (csp.mediaSrc) parts.push(`media-src ${csp.mediaSrc.join(" ")}`);
|
|
296
|
+
if (csp.frameSrc) parts.push(`frame-src ${csp.frameSrc.join(" ")}`);
|
|
297
|
+
return parts.join("; ");
|
|
298
|
+
}
|
|
299
|
+
buildHSTS() {
|
|
300
|
+
const hsts = this.config.hsts;
|
|
301
|
+
if (!hsts || !hsts.enabled) return "";
|
|
302
|
+
let value = `max-age=${hsts.maxAge}`;
|
|
303
|
+
if (hsts.includeSubDomains) value += "; includeSubDomains";
|
|
304
|
+
if (hsts.preload) value += "; preload";
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
middleware() {
|
|
308
|
+
return (req, res, next) => {
|
|
309
|
+
if (this.config.xFrameOptions) {
|
|
310
|
+
res.setHeader("X-Frame-Options", this.config.xFrameOptions);
|
|
311
|
+
}
|
|
312
|
+
if (this.config.xContentTypeOptions) {
|
|
313
|
+
res.setHeader("X-Content-Type-Options", this.config.xContentTypeOptions);
|
|
314
|
+
}
|
|
315
|
+
if (this.config.xssProtection) {
|
|
316
|
+
res.setHeader("X-XSS-Protection", this.config.xssProtection);
|
|
317
|
+
}
|
|
318
|
+
if (this.config.referrerPolicy) {
|
|
319
|
+
res.setHeader("Referrer-Policy", this.config.referrerPolicy);
|
|
320
|
+
}
|
|
321
|
+
if (this.config.contentSecurityPolicy) {
|
|
322
|
+
const csp = this.buildCSP();
|
|
323
|
+
if (csp) {
|
|
324
|
+
res.setHeader("Content-Security-Policy", csp);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (this.config.hsts && this.config.hsts.enabled) {
|
|
328
|
+
const hsts = this.buildHSTS();
|
|
329
|
+
if (hsts) {
|
|
330
|
+
res.setHeader("Strict-Transport-Security", hsts);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (this.config.permissionsPolicy && Object.keys(this.config.permissionsPolicy).length > 0) {
|
|
334
|
+
const permissions = Object.entries(this.config.permissionsPolicy).map(([key, value]) => `${key}=(${value.join(" ")})`).join(", ");
|
|
335
|
+
res.setHeader("Permissions-Policy", permissions);
|
|
336
|
+
}
|
|
337
|
+
res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
|
|
338
|
+
res.setHeader("X-Download-Options", "noopen");
|
|
339
|
+
next();
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
getConfig() {
|
|
343
|
+
return this.config;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
SecurityHeaders_default = SecurityHeaders;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// src/modules/cors/CORSManager.ts
|
|
351
|
+
var CORSManager_exports = {};
|
|
352
|
+
__export(CORSManager_exports, {
|
|
353
|
+
CORSManager: () => CORSManager,
|
|
354
|
+
default: () => CORSManager_default
|
|
355
|
+
});
|
|
356
|
+
var CORSManager, CORSManager_default;
|
|
357
|
+
var init_CORSManager = __esm({
|
|
358
|
+
"src/modules/cors/CORSManager.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
CORSManager = class {
|
|
361
|
+
config;
|
|
362
|
+
logger;
|
|
363
|
+
constructor(config, logger2) {
|
|
364
|
+
this.config = config;
|
|
365
|
+
this.logger = logger2;
|
|
366
|
+
}
|
|
367
|
+
isOriginAllowed(origin) {
|
|
368
|
+
if (!origin) return this.config.origins.includes("*");
|
|
369
|
+
return this.config.origins.some((allowed) => {
|
|
370
|
+
if (allowed === "*") return true;
|
|
371
|
+
if (allowed === origin) return true;
|
|
372
|
+
if (allowed.startsWith("*.")) {
|
|
373
|
+
const base = allowed.slice(2);
|
|
374
|
+
return origin.endsWith(base);
|
|
375
|
+
}
|
|
376
|
+
if (allowed.includes("*")) {
|
|
377
|
+
const regex = new RegExp("^" + allowed.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
|
|
378
|
+
return regex.test(origin);
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
middleware() {
|
|
384
|
+
return (req, res, next) => {
|
|
385
|
+
const origin = req.headers.origin || req.headers.referer;
|
|
386
|
+
const method = req.method;
|
|
387
|
+
if (method === "OPTIONS") {
|
|
388
|
+
if (this.isOriginAllowed(origin)) {
|
|
389
|
+
if (this.config.credentials) {
|
|
390
|
+
res.setHeader("Access-Control-Allow-Origin", origin || "*");
|
|
391
|
+
} else {
|
|
392
|
+
res.setHeader("Access-Control-Allow-Origin", this.config.origins.includes("*") ? "*" : origin || "");
|
|
393
|
+
}
|
|
394
|
+
res.setHeader("Access-Control-Allow-Methods", this.config.methods?.join(", ") || "GET, POST, PUT, DELETE, OPTIONS");
|
|
395
|
+
res.setHeader("Access-Control-Allow-Headers", this.config.allowedHeaders?.join(", ") || "Content-Type, Authorization");
|
|
396
|
+
res.setHeader("Access-Control-Allow-Credentials", String(this.config.credentials));
|
|
397
|
+
if (this.config.maxAge) {
|
|
398
|
+
res.setHeader("Access-Control-Max-Age", String(this.config.maxAge));
|
|
399
|
+
}
|
|
400
|
+
if (this.config.exposedHeaders?.length) {
|
|
401
|
+
res.setHeader("Access-Control-Expose-Headers", this.config.exposedHeaders.join(", "));
|
|
402
|
+
}
|
|
403
|
+
if (this.config.preflightContinue) {
|
|
404
|
+
return next();
|
|
405
|
+
}
|
|
406
|
+
return res.status(204).end();
|
|
407
|
+
}
|
|
408
|
+
return res.status(403).json({ error: "CORS: Origin not allowed" });
|
|
409
|
+
}
|
|
410
|
+
if (this.isOriginAllowed(origin)) {
|
|
411
|
+
if (this.config.credentials) {
|
|
412
|
+
res.setHeader("Access-Control-Allow-Origin", origin || "*");
|
|
413
|
+
} else {
|
|
414
|
+
res.setHeader("Access-Control-Allow-Origin", this.config.origins.includes("*") ? "*" : origin || "");
|
|
415
|
+
}
|
|
416
|
+
if (this.config.credentials) {
|
|
417
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
418
|
+
}
|
|
419
|
+
if (this.config.exposedHeaders?.length) {
|
|
420
|
+
res.setHeader("Access-Control-Expose-Headers", this.config.exposedHeaders.join(", "));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
next();
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
addOrigin(origin) {
|
|
427
|
+
if (!this.config.origins.includes(origin)) {
|
|
428
|
+
this.config.origins.push(origin);
|
|
429
|
+
this.logger.info(`Added CORS origin: ${origin}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
removeOrigin(origin) {
|
|
433
|
+
const index = this.config.origins.indexOf(origin);
|
|
434
|
+
if (index > -1) {
|
|
435
|
+
this.config.origins.splice(index, 1);
|
|
436
|
+
this.logger.info(`Removed CORS origin: ${origin}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getConfig() {
|
|
440
|
+
return this.config;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
CORSManager_default = CORSManager;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// src/modules/xss/XSSProtection.ts
|
|
448
|
+
var XSSProtection_exports = {};
|
|
449
|
+
__export(XSSProtection_exports, {
|
|
450
|
+
XSSProtection: () => XSSProtection,
|
|
451
|
+
default: () => XSSProtection_default
|
|
452
|
+
});
|
|
453
|
+
var XSSProtection, XSSProtection_default;
|
|
454
|
+
var init_XSSProtection = __esm({
|
|
455
|
+
"src/modules/xss/XSSProtection.ts"() {
|
|
456
|
+
"use strict";
|
|
457
|
+
XSSProtection = class {
|
|
458
|
+
config;
|
|
459
|
+
logger;
|
|
460
|
+
patterns = {
|
|
461
|
+
low: /<script|javascript:|on\w+\s*=/i,
|
|
462
|
+
medium: /<script|javascript:|on\w+\s*=|<iframe|<object|<embed|eval\(|innerHTML|outerHTML/i,
|
|
463
|
+
strict: /<script|javascript:|on\w+\s*=|<iframe|<object|<embed|<embed|<applet|<meta|<link|<style|<body|<img|<input|<form|<select|<textarea|<datalist|<keygen|<output|<details|<dialog|<menu|<menuitem|eval\(|innerHTML|outerHTML|document\.|window\.|alert\(|confirm\(|prompt\(/i
|
|
464
|
+
};
|
|
465
|
+
customRules = [];
|
|
466
|
+
constructor(config, logger2) {
|
|
467
|
+
this.config = config;
|
|
468
|
+
this.logger = logger2;
|
|
469
|
+
if (this.config.customRules) {
|
|
470
|
+
for (const [name, pattern] of Object.entries(this.config.customRules)) {
|
|
471
|
+
this.customRules.push({ pattern: new RegExp(pattern, "i"), name });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
sanitizeString(str) {
|
|
476
|
+
if (typeof str !== "string") return str;
|
|
477
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
478
|
+
}
|
|
479
|
+
check(data) {
|
|
480
|
+
const result = this.deepCheck(data);
|
|
481
|
+
if (result.detected) {
|
|
482
|
+
this.logger.warn("XSS attempt detected", {
|
|
483
|
+
patterns: result.patterns,
|
|
484
|
+
level: this.config.level
|
|
485
|
+
});
|
|
486
|
+
if (this.config.level === "strict") {
|
|
487
|
+
return {
|
|
488
|
+
allowed: false,
|
|
489
|
+
reason: `XSS protection: Suspicious input detected - ${result.patterns.join(", ")}`
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return { allowed: true };
|
|
494
|
+
}
|
|
495
|
+
deepCheck(obj, path3 = "") {
|
|
496
|
+
if (obj === null || obj === void 0) {
|
|
497
|
+
return { detected: false, patterns: [] };
|
|
498
|
+
}
|
|
499
|
+
if (typeof obj === "string") {
|
|
500
|
+
return this.checkString(obj, path3);
|
|
501
|
+
}
|
|
502
|
+
if (typeof obj === "object") {
|
|
503
|
+
const patterns = [];
|
|
504
|
+
let detected = false;
|
|
505
|
+
for (const key of Object.keys(obj)) {
|
|
506
|
+
const result = this.deepCheck(obj[key], path3 ? `${path3}.${key}` : key);
|
|
507
|
+
if (result.detected) {
|
|
508
|
+
detected = true;
|
|
509
|
+
patterns.push(...result.patterns);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { detected, patterns };
|
|
513
|
+
}
|
|
514
|
+
return { detected: false, patterns: [] };
|
|
515
|
+
}
|
|
516
|
+
checkString(str, path3) {
|
|
517
|
+
const patterns = [];
|
|
518
|
+
const basePattern = this.patterns[this.config.level];
|
|
519
|
+
if (basePattern.test(str)) {
|
|
520
|
+
patterns.push(`base-${this.config.level}`);
|
|
521
|
+
}
|
|
522
|
+
for (const rule of this.customRules) {
|
|
523
|
+
if (rule.pattern.test(str)) {
|
|
524
|
+
patterns.push(rule.name);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return { detected: patterns.length > 0, patterns };
|
|
528
|
+
}
|
|
529
|
+
sanitize(data) {
|
|
530
|
+
if (typeof data === "string") {
|
|
531
|
+
return this.sanitizeString(data);
|
|
532
|
+
}
|
|
533
|
+
if (Array.isArray(data)) {
|
|
534
|
+
return data.map((item) => this.sanitize(item));
|
|
535
|
+
}
|
|
536
|
+
if (typeof data === "object" && data !== null) {
|
|
537
|
+
const sanitized = {};
|
|
538
|
+
for (const [key, value] of Object.entries(data)) {
|
|
539
|
+
sanitized[key] = this.sanitize(value);
|
|
540
|
+
}
|
|
541
|
+
return sanitized;
|
|
542
|
+
}
|
|
543
|
+
return data;
|
|
544
|
+
}
|
|
545
|
+
middleware() {
|
|
546
|
+
return (req, res, next) => {
|
|
547
|
+
if (req.body) {
|
|
548
|
+
const result = this.check(req.body);
|
|
549
|
+
if (!result.allowed) {
|
|
550
|
+
return res.status(400).json({
|
|
551
|
+
error: "Bad Request",
|
|
552
|
+
message: result.reason
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
req.body = this.sanitize(req.body);
|
|
556
|
+
}
|
|
557
|
+
if (req.query) {
|
|
558
|
+
const result = this.check(req.query);
|
|
559
|
+
if (!result.allowed) {
|
|
560
|
+
return res.status(400).json({
|
|
561
|
+
error: "Bad Request",
|
|
562
|
+
message: result.reason
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
next();
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
addCustomRule(name, pattern) {
|
|
570
|
+
this.customRules.push({ pattern: new RegExp(pattern, "i"), name });
|
|
571
|
+
}
|
|
572
|
+
getConfig() {
|
|
573
|
+
return this.config;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
XSSProtection_default = XSSProtection;
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// src/modules/csrf/CSRFProtection.ts
|
|
581
|
+
var CSRFProtection_exports = {};
|
|
582
|
+
__export(CSRFProtection_exports, {
|
|
583
|
+
CSRFProtection: () => CSRFProtection,
|
|
584
|
+
default: () => CSRFProtection_default
|
|
585
|
+
});
|
|
586
|
+
var import_crypto, CSRFProtection, CSRFProtection_default;
|
|
587
|
+
var init_CSRFProtection = __esm({
|
|
588
|
+
"src/modules/csrf/CSRFProtection.ts"() {
|
|
589
|
+
"use strict";
|
|
590
|
+
import_crypto = __toESM(require("crypto"), 1);
|
|
591
|
+
CSRFProtection = class {
|
|
592
|
+
config;
|
|
593
|
+
logger;
|
|
594
|
+
tokens = /* @__PURE__ */ new Map();
|
|
595
|
+
cleanupInterval = null;
|
|
596
|
+
constructor(config, logger2) {
|
|
597
|
+
this.config = config;
|
|
598
|
+
this.logger = logger2;
|
|
599
|
+
this.startCleanup();
|
|
600
|
+
}
|
|
601
|
+
startCleanup() {
|
|
602
|
+
this.cleanupInterval = setInterval(() => {
|
|
603
|
+
const now = Date.now();
|
|
604
|
+
const expiryMs = 36e5;
|
|
605
|
+
for (const [key, token] of this.tokens.entries()) {
|
|
606
|
+
if (now - this.hashToTimestamp(token) > expiryMs) {
|
|
607
|
+
this.tokens.delete(key);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}, 3e5);
|
|
611
|
+
}
|
|
612
|
+
hashToTimestamp(token) {
|
|
613
|
+
const hash = token.substring(0, 8);
|
|
614
|
+
return parseInt(hash, 16) * 1e3;
|
|
615
|
+
}
|
|
616
|
+
generateToken(sessionId) {
|
|
617
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString(16);
|
|
618
|
+
const random = import_crypto.default.randomBytes(this.config.tokenLength || 32).toString("hex");
|
|
619
|
+
const token = timestamp + random;
|
|
620
|
+
this.tokens.set(sessionId, token);
|
|
621
|
+
return token;
|
|
622
|
+
}
|
|
623
|
+
validateToken(token, sessionId) {
|
|
624
|
+
const storedToken = this.tokens.get(sessionId);
|
|
625
|
+
if (!storedToken) return false;
|
|
626
|
+
if (storedToken !== token) {
|
|
627
|
+
this.logger.warn("CSRF token mismatch", { sessionId });
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
const tokenTimestamp = this.hashToTimestamp(token);
|
|
631
|
+
const now = Date.now();
|
|
632
|
+
const maxAge = 36e5;
|
|
633
|
+
if (now - tokenTimestamp > maxAge) {
|
|
634
|
+
this.logger.warn("CSRF token expired", { sessionId });
|
|
635
|
+
this.tokens.delete(sessionId);
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
middleware() {
|
|
641
|
+
return (req, res, next) => {
|
|
642
|
+
const sessionId = req.session?.id || req.headers["x-session-id"] || req.ip;
|
|
643
|
+
if (req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS") {
|
|
644
|
+
const token2 = this.generateToken(sessionId);
|
|
645
|
+
if (this.config.cookie) {
|
|
646
|
+
res.cookie(this.config.cookie.name || "_csrf", token2, {
|
|
647
|
+
httpOnly: this.config.cookie.httpOnly !== false,
|
|
648
|
+
secure: this.config.cookie.secure !== false,
|
|
649
|
+
sameSite: this.config.cookie.sameSite || "strict",
|
|
650
|
+
maxAge: 36e5
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
res.locals.csrfToken = token2;
|
|
654
|
+
return next();
|
|
655
|
+
}
|
|
656
|
+
const token = req.body?.csrf || req.headers["x-csrf-token"] || req.headers["x-xsrf-token"];
|
|
657
|
+
if (!token) {
|
|
658
|
+
this.logger.warn("CSRF token missing", {
|
|
659
|
+
method: req.method,
|
|
660
|
+
path: req.path
|
|
661
|
+
});
|
|
662
|
+
return res.status(403).json({
|
|
663
|
+
error: "Forbidden",
|
|
664
|
+
message: "CSRF token missing"
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
if (!this.validateToken(token, sessionId)) {
|
|
668
|
+
return res.status(403).json({
|
|
669
|
+
error: "Forbidden",
|
|
670
|
+
message: "Invalid or expired CSRF token"
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
next();
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
getToken(sessionId) {
|
|
677
|
+
return this.generateToken(sessionId);
|
|
678
|
+
}
|
|
679
|
+
destroy() {
|
|
680
|
+
if (this.cleanupInterval) {
|
|
681
|
+
clearInterval(this.cleanupInterval);
|
|
682
|
+
}
|
|
683
|
+
this.tokens.clear();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
CSRFProtection_default = CSRFProtection;
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// src/modules/sql-injection/SQLInjectionProtection.ts
|
|
691
|
+
var SQLInjectionProtection_exports = {};
|
|
692
|
+
__export(SQLInjectionProtection_exports, {
|
|
693
|
+
SQLInjectionProtection: () => SQLInjectionProtection,
|
|
694
|
+
default: () => SQLInjectionProtection_default
|
|
695
|
+
});
|
|
696
|
+
var SQLInjectionProtection, SQLInjectionProtection_default;
|
|
697
|
+
var init_SQLInjectionProtection = __esm({
|
|
698
|
+
"src/modules/sql-injection/SQLInjectionProtection.ts"() {
|
|
699
|
+
"use strict";
|
|
700
|
+
SQLInjectionProtection = class {
|
|
701
|
+
config;
|
|
702
|
+
logger;
|
|
703
|
+
customPatterns = [];
|
|
704
|
+
defaultPatterns = [
|
|
705
|
+
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|TRUNCATE)\b.*\b(FROM|INTO|TABLE|DATABASE)\b)/i,
|
|
706
|
+
/((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/i,
|
|
707
|
+
/((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/i,
|
|
708
|
+
/((\%27)|(\'))\s*((\%6F)|o|(\%4F))((\%72)|r|(\%52))/i,
|
|
709
|
+
/(char\s*\(\s*\d+\s*\))/i,
|
|
710
|
+
/(\%77here\s+\w+\s*=\s*\w+)/i,
|
|
711
|
+
/(\hre\b.*\b\w+\s*=\s*\w+)/i,
|
|
712
|
+
/(union\s+select\s+)/i,
|
|
713
|
+
/(union\s+all\s+select\s+)/i,
|
|
714
|
+
/(select\s+.*\s+from\s+)/i,
|
|
715
|
+
/(insert\s+into\s+)/i,
|
|
716
|
+
/(delete\s+from\s+)/i,
|
|
717
|
+
/(update\s+.*\s+set\s+)/i,
|
|
718
|
+
/(drop\s+table\s+)/i,
|
|
719
|
+
/(drop\s+database\s+)/i,
|
|
720
|
+
/(exec\s*\(|execute\s*\(|xp_)/i,
|
|
721
|
+
/(0x[0-9a-fA-F]+)/i,
|
|
722
|
+
/(\bOR\b\s+\b\d+\s*=\s*\d+)/i,
|
|
723
|
+
/(\bAND\b\s+\b\d+\s*=\s*\d+)/i,
|
|
724
|
+
/(\'\s*OR\s*\'\s*\'\s*=\s*\'\s*)/i,
|
|
725
|
+
/(\'\s*OR\s+\w+\s*=\s*\w+)/i,
|
|
726
|
+
/(--\s*$)/m,
|
|
727
|
+
/(;\s*drop\s+)/i,
|
|
728
|
+
/(;\s*exec\s+)/i,
|
|
729
|
+
/(\/\*.*\*\/)/,
|
|
730
|
+
/(\bhaving\b\s+\w+\s*[=<>])/i,
|
|
731
|
+
/(\blimit\b\s+\d+(,\s*\d+)?)/i
|
|
732
|
+
];
|
|
733
|
+
constructor(config, logger2) {
|
|
734
|
+
this.config = config;
|
|
735
|
+
this.logger = logger2;
|
|
736
|
+
if (this.config.customPatterns) {
|
|
737
|
+
for (const pattern of this.config.customPatterns) {
|
|
738
|
+
this.customPatterns.push(new RegExp(pattern, "i"));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
check(data) {
|
|
743
|
+
const allInput = this.extractInput(data);
|
|
744
|
+
const detectedPatterns = [];
|
|
745
|
+
for (const input of allInput) {
|
|
746
|
+
if (typeof input !== "string") continue;
|
|
747
|
+
for (const pattern of this.defaultPatterns) {
|
|
748
|
+
if (pattern.test(input)) {
|
|
749
|
+
detectedPatterns.push(`default-${pattern.toString()}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
for (const pattern of this.customPatterns) {
|
|
753
|
+
if (pattern.test(input)) {
|
|
754
|
+
detectedPatterns.push(`custom-${pattern.toString()}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (this.hasSuspiciousPatterns(input)) {
|
|
758
|
+
detectedPatterns.push("suspicious-comments-union");
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (detectedPatterns.length > 0) {
|
|
762
|
+
this.logger.warn("SQL Injection attempt detected", {
|
|
763
|
+
ip: data.ip,
|
|
764
|
+
url: data.url,
|
|
765
|
+
patterns: detectedPatterns
|
|
766
|
+
});
|
|
767
|
+
if (this.config.blockOnDetect || !this.config.logOnly) {
|
|
768
|
+
return {
|
|
769
|
+
allowed: false,
|
|
770
|
+
reason: "SQL Injection protection: Suspicious input detected",
|
|
771
|
+
detected: detectedPatterns.join(", ")
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return { allowed: true };
|
|
776
|
+
}
|
|
777
|
+
extractInput(data) {
|
|
778
|
+
const inputs = [];
|
|
779
|
+
if (data.body) {
|
|
780
|
+
inputs.push(...this.flattenObject(data.body));
|
|
781
|
+
}
|
|
782
|
+
if (data.url) {
|
|
783
|
+
const url = new URL(data.url, "http://localhost");
|
|
784
|
+
for (const [, value] of url.searchParams) {
|
|
785
|
+
inputs.push(value);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return inputs;
|
|
789
|
+
}
|
|
790
|
+
flattenObject(obj, prefix = "") {
|
|
791
|
+
const result = [];
|
|
792
|
+
if (obj === null || obj === void 0) {
|
|
793
|
+
return result;
|
|
794
|
+
}
|
|
795
|
+
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
|
|
796
|
+
result.push(String(obj));
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
799
|
+
if (Array.isArray(obj)) {
|
|
800
|
+
for (const item of obj) {
|
|
801
|
+
result.push(...this.flattenObject(item));
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
if (typeof obj === "object") {
|
|
806
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
807
|
+
result.push(...this.flattenObject(value, `${prefix}.${key}`));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return result;
|
|
811
|
+
}
|
|
812
|
+
hasSuspiciousPatterns(input) {
|
|
813
|
+
const suspicious = [
|
|
814
|
+
/\/\*.*\*\//,
|
|
815
|
+
/;\s*(drop|insert|update|delete|create|alter)/i,
|
|
816
|
+
/@@\w+/,
|
|
817
|
+
/0x[0-9a-fA-F]{8,}/,
|
|
818
|
+
/waitfor\s+delay/i,
|
|
819
|
+
/benchmark\s*\(/i,
|
|
820
|
+
/sleep\s*\(/i
|
|
821
|
+
];
|
|
822
|
+
return suspicious.some((pattern) => pattern.test(input));
|
|
823
|
+
}
|
|
824
|
+
middleware() {
|
|
825
|
+
return (req, res, next) => {
|
|
826
|
+
const data = {
|
|
827
|
+
ip: req.ip || req.connection?.remoteAddress,
|
|
828
|
+
method: req.method,
|
|
829
|
+
url: req.url,
|
|
830
|
+
headers: req.headers,
|
|
831
|
+
body: req.body,
|
|
832
|
+
userAgent: req.headers?.["user-agent"]
|
|
833
|
+
};
|
|
834
|
+
const result = this.check(data);
|
|
835
|
+
if (!result.allowed) {
|
|
836
|
+
if (this.config.blockOnDetect) {
|
|
837
|
+
return res.status(403).json({
|
|
838
|
+
error: "Forbidden",
|
|
839
|
+
message: result.reason
|
|
840
|
+
});
|
|
841
|
+
} else {
|
|
842
|
+
this.logger.warn("SQL Injection detected (log only)", { reason: result.reason });
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
next();
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
addPattern(pattern) {
|
|
849
|
+
this.customPatterns.push(new RegExp(pattern, "i"));
|
|
850
|
+
}
|
|
851
|
+
getConfig() {
|
|
852
|
+
return this.config;
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
SQLInjectionProtection_default = SQLInjectionProtection;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// src/modules/bot-protection/BotProtection.ts
|
|
860
|
+
var BotProtection_exports = {};
|
|
861
|
+
__export(BotProtection_exports, {
|
|
862
|
+
BotProtection: () => BotProtection,
|
|
863
|
+
default: () => BotProtection_default
|
|
864
|
+
});
|
|
865
|
+
var BotProtection, BotProtection_default;
|
|
866
|
+
var init_BotProtection = __esm({
|
|
867
|
+
"src/modules/bot-protection/BotProtection.ts"() {
|
|
868
|
+
"use strict";
|
|
869
|
+
BotProtection = class {
|
|
870
|
+
config;
|
|
871
|
+
logger;
|
|
872
|
+
challengeCache = /* @__PURE__ */ new Map();
|
|
873
|
+
cleanupInterval = null;
|
|
874
|
+
knownBotPatterns = [
|
|
875
|
+
/bot/i,
|
|
876
|
+
/crawler/i,
|
|
877
|
+
/spider/i,
|
|
878
|
+
/scraper/i,
|
|
879
|
+
/curl/i,
|
|
880
|
+
/wget/i,
|
|
881
|
+
/python-requests/i,
|
|
882
|
+
/node-fetch/i,
|
|
883
|
+
/go-http/i,
|
|
884
|
+
/java\//i,
|
|
885
|
+
/httpclient/i,
|
|
886
|
+
/fetch/i
|
|
887
|
+
];
|
|
888
|
+
constructor(config, logger2) {
|
|
889
|
+
this.config = config;
|
|
890
|
+
this.logger = logger2;
|
|
891
|
+
this.startCleanup();
|
|
892
|
+
}
|
|
893
|
+
startCleanup() {
|
|
894
|
+
this.cleanupInterval = setInterval(() => {
|
|
895
|
+
const now = Date.now();
|
|
896
|
+
for (const [key, value] of this.challengeCache.entries()) {
|
|
897
|
+
if (now > value.expires) {
|
|
898
|
+
this.challengeCache.delete(key);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}, 6e4);
|
|
902
|
+
}
|
|
903
|
+
isKnownBot(userAgent) {
|
|
904
|
+
for (const pattern of this.knownBotPatterns) {
|
|
905
|
+
if (pattern.test(userAgent)) {
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (this.config.blockUserAgents && this.config.blockUserAgents.length > 0) {
|
|
910
|
+
for (const pattern of this.config.blockUserAgents) {
|
|
911
|
+
if (new RegExp(pattern, "i").test(userAgent)) {
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (this.config.allowUserAgents && this.config.allowUserAgents.length > 0) {
|
|
917
|
+
for (const pattern of this.config.allowUserAgents) {
|
|
918
|
+
if (new RegExp(pattern, "i").test(userAgent)) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
generateChallenge() {
|
|
926
|
+
const a = Math.floor(Math.random() * 10) + 1;
|
|
927
|
+
const b = Math.floor(Math.random() * 10) + 1;
|
|
928
|
+
const answer = (a + b).toString();
|
|
929
|
+
const question = `${a} + ${b} = ?`;
|
|
930
|
+
return { question, answer };
|
|
931
|
+
}
|
|
932
|
+
async check(data) {
|
|
933
|
+
const userAgent = data.userAgent || "";
|
|
934
|
+
const ip = data.ip || "unknown";
|
|
935
|
+
if (this.isKnownBot(userAgent)) {
|
|
936
|
+
if (this.config.challengeEnabled) {
|
|
937
|
+
const cacheKey = `${ip}:${data.url}`;
|
|
938
|
+
const challengeData = this.challengeCache.get(cacheKey);
|
|
939
|
+
if (!challengeData || !challengeData.solved) {
|
|
940
|
+
const challenge = this.generateChallenge();
|
|
941
|
+
this.challengeCache.set(cacheKey, {
|
|
942
|
+
solved: false,
|
|
943
|
+
expires: Date.now() + this.config.challengeExpiryMs
|
|
944
|
+
});
|
|
945
|
+
this.logger.info("Bot challenge issued", { ip, userAgent });
|
|
946
|
+
return {
|
|
947
|
+
allowed: false,
|
|
948
|
+
reason: "Bot verification required",
|
|
949
|
+
challenge: {
|
|
950
|
+
question: challenge.question,
|
|
951
|
+
cookieName: this.config.challengeCookieName
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
return {
|
|
957
|
+
allowed: false,
|
|
958
|
+
reason: "Bot detected and blocked"
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return { allowed: true };
|
|
963
|
+
}
|
|
964
|
+
verifyChallenge(ip, url, answer) {
|
|
965
|
+
const cacheKey = `${ip}:${url}`;
|
|
966
|
+
const challengeData = this.challengeCache.get(cacheKey);
|
|
967
|
+
if (!challengeData) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
const storedChallenge = this.challengeCache.get(cacheKey + ":answer");
|
|
971
|
+
if (!storedChallenge) {
|
|
972
|
+
const newChallenge = this.generateChallenge();
|
|
973
|
+
this.challengeCache.set(cacheKey + ":answer", {
|
|
974
|
+
solved: false,
|
|
975
|
+
expires: Date.now() + 6e4
|
|
976
|
+
});
|
|
977
|
+
return newChallenge.answer === answer;
|
|
978
|
+
}
|
|
979
|
+
return storedChallenge.solved;
|
|
980
|
+
}
|
|
981
|
+
solveChallenge(ip, url) {
|
|
982
|
+
const cacheKey = `${ip}:${url}`;
|
|
983
|
+
const data = this.challengeCache.get(cacheKey);
|
|
984
|
+
if (data) {
|
|
985
|
+
data.solved = true;
|
|
986
|
+
this.challengeCache.set(cacheKey, data);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
middleware() {
|
|
990
|
+
return async (req, res, next) => {
|
|
991
|
+
const ip = req.ip || req.connection?.remoteAddress || "unknown";
|
|
992
|
+
const userAgent = req.headers?.["user-agent"] || "";
|
|
993
|
+
const data = {
|
|
994
|
+
ip,
|
|
995
|
+
method: req.method,
|
|
996
|
+
url: req.url,
|
|
997
|
+
headers: req.headers,
|
|
998
|
+
userAgent
|
|
999
|
+
};
|
|
1000
|
+
const challengeCookie = req.cookies?.[this.config.challengeCookieName || "webarmor_challenge"];
|
|
1001
|
+
if (challengeCookie) {
|
|
1002
|
+
try {
|
|
1003
|
+
const decoded = Buffer.from(challengeCookie, "base64").toString();
|
|
1004
|
+
const [storedIp, timestamp] = decoded.split(":");
|
|
1005
|
+
if (storedIp === ip && Date.now() - parseInt(timestamp) < this.config.challengeExpiryMs) {
|
|
1006
|
+
return next();
|
|
1007
|
+
}
|
|
1008
|
+
} catch (e) {
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const result = await this.check(data);
|
|
1012
|
+
if (!result.allowed) {
|
|
1013
|
+
if (result.challenge) {
|
|
1014
|
+
res.setHeader("X-WebArmor-Challenge", "required");
|
|
1015
|
+
return res.status(403).json({
|
|
1016
|
+
error: "Bot Verification Required",
|
|
1017
|
+
message: result.reason,
|
|
1018
|
+
challenge: result.challenge
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
return res.status(403).json({
|
|
1022
|
+
error: "Forbidden",
|
|
1023
|
+
message: result.reason
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
next();
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
getConfig() {
|
|
1030
|
+
return this.config;
|
|
1031
|
+
}
|
|
1032
|
+
destroy() {
|
|
1033
|
+
if (this.cleanupInterval) {
|
|
1034
|
+
clearInterval(this.cleanupInterval);
|
|
1035
|
+
}
|
|
1036
|
+
this.challengeCache.clear();
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
BotProtection_default = BotProtection;
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// src/modules/ip-filter/IPFilter.ts
|
|
1044
|
+
var IPFilter_exports = {};
|
|
1045
|
+
__export(IPFilter_exports, {
|
|
1046
|
+
IPFilter: () => IPFilter,
|
|
1047
|
+
default: () => IPFilter_default
|
|
1048
|
+
});
|
|
1049
|
+
var IPFilter, IPFilter_default;
|
|
1050
|
+
var init_IPFilter = __esm({
|
|
1051
|
+
"src/modules/ip-filter/IPFilter.ts"() {
|
|
1052
|
+
"use strict";
|
|
1053
|
+
IPFilter = class {
|
|
1054
|
+
config;
|
|
1055
|
+
logger;
|
|
1056
|
+
customBlockedIPs = /* @__PURE__ */ new Set();
|
|
1057
|
+
constructor(config, logger2) {
|
|
1058
|
+
this.config = config;
|
|
1059
|
+
this.logger = logger2;
|
|
1060
|
+
}
|
|
1061
|
+
parseIP(ip) {
|
|
1062
|
+
if (ip.includes("/")) {
|
|
1063
|
+
return { ip, isIPv6: ip.includes(":"), isSubnet: true };
|
|
1064
|
+
}
|
|
1065
|
+
return { ip, isIPv6: ip.includes(":"), isSubnet: false };
|
|
1066
|
+
}
|
|
1067
|
+
isIPInRange(ip, range) {
|
|
1068
|
+
if (!range.includes("/")) {
|
|
1069
|
+
return ip === range;
|
|
1070
|
+
}
|
|
1071
|
+
const [subnet, mask] = range.split("/");
|
|
1072
|
+
let maskNum = parseInt(mask);
|
|
1073
|
+
if (maskNum >= 32) {
|
|
1074
|
+
return ip === subnet;
|
|
1075
|
+
}
|
|
1076
|
+
const ipParts = ip.split(".").map(Number);
|
|
1077
|
+
const subnetParts = subnet.split(".").map(Number);
|
|
1078
|
+
const maskParts = [];
|
|
1079
|
+
for (let i = 0; i < 4; i++) {
|
|
1080
|
+
if (maskNum >= 8) {
|
|
1081
|
+
maskParts.push(255);
|
|
1082
|
+
maskNum -= 8;
|
|
1083
|
+
} else if (maskNum > 0) {
|
|
1084
|
+
maskParts.push(256 - Math.pow(2, 8 - maskNum));
|
|
1085
|
+
maskNum = 0;
|
|
1086
|
+
} else {
|
|
1087
|
+
maskParts.push(0);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
for (let i = 0; i < 4; i++) {
|
|
1091
|
+
if ((ipParts[i] & maskParts[i]) !== (subnetParts[i] & maskParts[i])) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
getClientIP(req) {
|
|
1098
|
+
if (this.config.useXForwardedFor) {
|
|
1099
|
+
const forwarded = req.headers?.["x-forwarded-for"];
|
|
1100
|
+
if (forwarded) {
|
|
1101
|
+
return forwarded.split(",")[0].trim();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || "127.0.0.1";
|
|
1105
|
+
}
|
|
1106
|
+
async check(ip) {
|
|
1107
|
+
const clientIP = this.parseIP(ip);
|
|
1108
|
+
if (this.config.whitelist.length > 0) {
|
|
1109
|
+
const isWhitelisted = this.config.whitelist.some(
|
|
1110
|
+
(range) => this.isIPInRange(ip, range)
|
|
1111
|
+
);
|
|
1112
|
+
if (!isWhitelisted) {
|
|
1113
|
+
if (this.config.logBlocked) {
|
|
1114
|
+
this.logger.warn("IP not in whitelist, blocked", { ip });
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
allowed: false,
|
|
1118
|
+
reason: "IP not in whitelist"
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return { allowed: true };
|
|
1122
|
+
}
|
|
1123
|
+
const isBlocked = this.config.blacklist.some(
|
|
1124
|
+
(range) => this.isIPInRange(ip, range)
|
|
1125
|
+
);
|
|
1126
|
+
if (isBlocked || this.customBlockedIPs.has(ip)) {
|
|
1127
|
+
if (this.config.logBlocked) {
|
|
1128
|
+
this.logger.warn("IP blocked by blacklist", { ip });
|
|
1129
|
+
}
|
|
1130
|
+
return {
|
|
1131
|
+
allowed: false,
|
|
1132
|
+
reason: "IP is blocked"
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
return { allowed: true };
|
|
1136
|
+
}
|
|
1137
|
+
addToWhitelist(ip) {
|
|
1138
|
+
if (!this.config.whitelist.includes(ip)) {
|
|
1139
|
+
this.config.whitelist.push(ip);
|
|
1140
|
+
this.logger.info(`Added to whitelist: ${ip}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
addToBlacklist(ip) {
|
|
1144
|
+
this.customBlockedIPs.add(ip);
|
|
1145
|
+
this.logger.info(`Added to blacklist: ${ip}`);
|
|
1146
|
+
}
|
|
1147
|
+
removeFromWhitelist(ip) {
|
|
1148
|
+
const index = this.config.whitelist.indexOf(ip);
|
|
1149
|
+
if (index > -1) {
|
|
1150
|
+
this.config.whitelist.splice(index, 1);
|
|
1151
|
+
this.logger.info(`Removed from whitelist: ${ip}`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
removeFromBlacklist(ip) {
|
|
1155
|
+
this.customBlockedIPs.delete(ip);
|
|
1156
|
+
this.logger.info(`Removed from blacklist: ${ip}`);
|
|
1157
|
+
}
|
|
1158
|
+
clearCustomBlacklist() {
|
|
1159
|
+
this.customBlockedIPs.clear();
|
|
1160
|
+
this.logger.info("Custom blacklist cleared");
|
|
1161
|
+
}
|
|
1162
|
+
middleware() {
|
|
1163
|
+
return async (req, res, next) => {
|
|
1164
|
+
const ip = this.getClientIP(req);
|
|
1165
|
+
const result = await this.check(ip);
|
|
1166
|
+
if (!result.allowed) {
|
|
1167
|
+
return res.status(403).json({
|
|
1168
|
+
error: "Forbidden",
|
|
1169
|
+
message: result.reason
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
next();
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
getConfig() {
|
|
1176
|
+
return this.config;
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
IPFilter_default = IPFilter;
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// src/modules/file-block/FileBlocker.ts
|
|
1184
|
+
var FileBlocker_exports = {};
|
|
1185
|
+
__export(FileBlocker_exports, {
|
|
1186
|
+
FileBlocker: () => FileBlocker,
|
|
1187
|
+
default: () => FileBlocker_default
|
|
1188
|
+
});
|
|
1189
|
+
var FileBlocker, FileBlocker_default;
|
|
1190
|
+
var init_FileBlocker = __esm({
|
|
1191
|
+
"src/modules/file-block/FileBlocker.ts"() {
|
|
1192
|
+
"use strict";
|
|
1193
|
+
FileBlocker = class {
|
|
1194
|
+
config;
|
|
1195
|
+
logger;
|
|
1196
|
+
constructor(config, logger2) {
|
|
1197
|
+
this.config = config;
|
|
1198
|
+
this.logger = logger2;
|
|
1199
|
+
}
|
|
1200
|
+
isPathBlocked(path3) {
|
|
1201
|
+
const normalizedPath = path3.toLowerCase();
|
|
1202
|
+
for (const blocked of this.config.blockedPaths) {
|
|
1203
|
+
if (normalizedPath.includes(blocked.toLowerCase())) {
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const sensitivePatterns = [
|
|
1208
|
+
/\.env$/i,
|
|
1209
|
+
/\.git\//i,
|
|
1210
|
+
/\.gitignore$/i,
|
|
1211
|
+
/config\.json$/i,
|
|
1212
|
+
/\.npmrc$/i,
|
|
1213
|
+
/\.aws\//i,
|
|
1214
|
+
/\.ssh\//i,
|
|
1215
|
+
/credentials\.json$/i,
|
|
1216
|
+
/\.pem$/i,
|
|
1217
|
+
/\.key$/i,
|
|
1218
|
+
/\.p12$/i
|
|
1219
|
+
];
|
|
1220
|
+
for (const pattern of sensitivePatterns) {
|
|
1221
|
+
if (pattern.test(normalizedPath)) {
|
|
1222
|
+
return true;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
for (const sensitive of this.config.protectSensitive) {
|
|
1226
|
+
if (normalizedPath.includes(sensitive.toLowerCase())) {
|
|
1227
|
+
return true;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
const ext = normalizedPath.split(".").pop();
|
|
1231
|
+
if (ext && this.config.blockedExtensions.includes(`.${ext}`)) {
|
|
1232
|
+
return true;
|
|
1233
|
+
}
|
|
1234
|
+
if (this.config.customRules && this.config.customRules.length > 0) {
|
|
1235
|
+
for (const rule of this.config.customRules) {
|
|
1236
|
+
const regex = new RegExp(rule.pattern, "i");
|
|
1237
|
+
if (regex.test(normalizedPath)) {
|
|
1238
|
+
return rule.action === "block";
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
check(path3) {
|
|
1245
|
+
if (this.isPathBlocked(path3)) {
|
|
1246
|
+
this.logger.warn("File access blocked", { path: path3 });
|
|
1247
|
+
return {
|
|
1248
|
+
allowed: false,
|
|
1249
|
+
reason: "Access to this path is denied"
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
return { allowed: true };
|
|
1253
|
+
}
|
|
1254
|
+
middleware() {
|
|
1255
|
+
return (req, res, next) => {
|
|
1256
|
+
const path3 = req.path || req.url;
|
|
1257
|
+
const result = this.check(path3);
|
|
1258
|
+
if (!result.allowed) {
|
|
1259
|
+
return res.status(403).json({
|
|
1260
|
+
error: "Forbidden",
|
|
1261
|
+
message: result.reason
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
next();
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
addBlockedPath(path3) {
|
|
1268
|
+
if (!this.config.blockedPaths.includes(path3)) {
|
|
1269
|
+
this.config.blockedPaths.push(path3);
|
|
1270
|
+
this.logger.info(`Added blocked path: ${path3}`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
removeBlockedPath(path3) {
|
|
1274
|
+
const index = this.config.blockedPaths.indexOf(path3);
|
|
1275
|
+
if (index > -1) {
|
|
1276
|
+
this.config.blockedPaths.splice(index, 1);
|
|
1277
|
+
this.logger.info(`Removed blocked path: ${path3}`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
addBlockedExtension(ext) {
|
|
1281
|
+
const formatted = ext.startsWith(".") ? ext : `.${ext}`;
|
|
1282
|
+
if (!this.config.blockedExtensions.includes(formatted)) {
|
|
1283
|
+
this.config.blockedExtensions.push(formatted);
|
|
1284
|
+
this.logger.info(`Added blocked extension: ${formatted}`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
removeBlockedExtension(ext) {
|
|
1288
|
+
const formatted = ext.startsWith(".") ? ext : `.${ext}`;
|
|
1289
|
+
const index = this.config.blockedExtensions.indexOf(formatted);
|
|
1290
|
+
if (index > -1) {
|
|
1291
|
+
this.config.blockedExtensions.splice(index, 1);
|
|
1292
|
+
this.logger.info(`Removed blocked extension: ${formatted}`);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
addCustomRule(pattern, action) {
|
|
1296
|
+
this.config.customRules?.push({ pattern, action });
|
|
1297
|
+
this.logger.info(`Added custom rule: ${pattern} -> ${action}`);
|
|
1298
|
+
}
|
|
1299
|
+
getConfig() {
|
|
1300
|
+
return this.config;
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
FileBlocker_default = FileBlocker;
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
// src/index.ts
|
|
1308
|
+
var index_exports = {};
|
|
1309
|
+
__export(index_exports, {
|
|
1310
|
+
ConfigEncryptor: () => ConfigEncryptor_default,
|
|
1311
|
+
DatabaseEncryptor: () => DatabaseEncryptor_default,
|
|
1312
|
+
FileEncryptor: () => FileEncryptor_default,
|
|
1313
|
+
Logger: () => Logger_default,
|
|
1314
|
+
SessionEncryptor: () => SessionEncryptor_default,
|
|
1315
|
+
Shield: () => Shield_default,
|
|
1316
|
+
createShield: () => createShield,
|
|
1317
|
+
default: () => index_default,
|
|
1318
|
+
security: () => security
|
|
1319
|
+
});
|
|
1320
|
+
module.exports = __toCommonJS(index_exports);
|
|
1321
|
+
|
|
1322
|
+
// src/core/Config.ts
|
|
1323
|
+
var defaultConfig = {
|
|
1324
|
+
framework: "express",
|
|
1325
|
+
debug: false,
|
|
1326
|
+
logLevel: "info",
|
|
1327
|
+
ddos: {
|
|
1328
|
+
enabled: true,
|
|
1329
|
+
maxRequests: 1e3,
|
|
1330
|
+
windowMs: 6e4,
|
|
1331
|
+
blockDurationMs: 3e5,
|
|
1332
|
+
scoreThreshold: 50
|
|
1333
|
+
},
|
|
1334
|
+
rateLimit: {
|
|
1335
|
+
enabled: true,
|
|
1336
|
+
windowMs: 6e4,
|
|
1337
|
+
max: 100,
|
|
1338
|
+
message: "Too many requests from this IP, please try again later.",
|
|
1339
|
+
statusCode: 429,
|
|
1340
|
+
skipSuccessfulRequests: false,
|
|
1341
|
+
keyGenerator: void 0
|
|
1342
|
+
},
|
|
1343
|
+
securityHeaders: {
|
|
1344
|
+
enabled: true,
|
|
1345
|
+
contentSecurityPolicy: {
|
|
1346
|
+
defaultSrc: ["'self'"],
|
|
1347
|
+
scriptSrc: ["'self'"],
|
|
1348
|
+
styleSrc: ["'self'"],
|
|
1349
|
+
imgSrc: ["'self'", "data:"]
|
|
1350
|
+
},
|
|
1351
|
+
hsts: {
|
|
1352
|
+
enabled: true,
|
|
1353
|
+
maxAge: 31536e3,
|
|
1354
|
+
includeSubDomains: true,
|
|
1355
|
+
preload: true
|
|
1356
|
+
},
|
|
1357
|
+
xFrameOptions: "DENY",
|
|
1358
|
+
xContentTypeOptions: "nosniff",
|
|
1359
|
+
xssProtection: "1; mode=block",
|
|
1360
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
1361
|
+
permissionsPolicy: {}
|
|
1362
|
+
},
|
|
1363
|
+
cors: {
|
|
1364
|
+
enabled: true,
|
|
1365
|
+
origins: ["*"],
|
|
1366
|
+
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
1367
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
1368
|
+
exposedHeaders: [],
|
|
1369
|
+
credentials: false,
|
|
1370
|
+
maxAge: 86400,
|
|
1371
|
+
preflightContinue: false
|
|
1372
|
+
},
|
|
1373
|
+
xss: {
|
|
1374
|
+
enabled: true,
|
|
1375
|
+
level: "medium",
|
|
1376
|
+
blockOnDetect: true,
|
|
1377
|
+
customRules: {}
|
|
1378
|
+
},
|
|
1379
|
+
csrf: {
|
|
1380
|
+
enabled: false,
|
|
1381
|
+
cookie: {
|
|
1382
|
+
name: "_csrf",
|
|
1383
|
+
httpOnly: true,
|
|
1384
|
+
secure: true,
|
|
1385
|
+
sameSite: "strict"
|
|
1386
|
+
},
|
|
1387
|
+
tokenLength: 32
|
|
1388
|
+
},
|
|
1389
|
+
sqlInjection: {
|
|
1390
|
+
enabled: true,
|
|
1391
|
+
blockOnDetect: true,
|
|
1392
|
+
logOnly: false,
|
|
1393
|
+
customPatterns: []
|
|
1394
|
+
},
|
|
1395
|
+
botProtection: {
|
|
1396
|
+
enabled: true,
|
|
1397
|
+
challengeEnabled: true,
|
|
1398
|
+
challengeExpiryMs: 36e5,
|
|
1399
|
+
challengeCookieName: "webarmor_challenge",
|
|
1400
|
+
allowUserAgents: [],
|
|
1401
|
+
blockUserAgents: []
|
|
1402
|
+
},
|
|
1403
|
+
ipFilter: {
|
|
1404
|
+
enabled: false,
|
|
1405
|
+
whitelist: [],
|
|
1406
|
+
blacklist: [],
|
|
1407
|
+
logBlocked: true,
|
|
1408
|
+
useXForwardedFor: true
|
|
1409
|
+
},
|
|
1410
|
+
fileBlock: {
|
|
1411
|
+
enabled: true,
|
|
1412
|
+
blockedPaths: [],
|
|
1413
|
+
blockedExtensions: [".exe", ".bat", ".cmd", ".sh", ".ps1"],
|
|
1414
|
+
protectSensitive: [".env", ".git", "config.json", ".npmrc", ".aws/credentials"],
|
|
1415
|
+
customRules: []
|
|
1416
|
+
},
|
|
1417
|
+
encryption: {
|
|
1418
|
+
enabled: false,
|
|
1419
|
+
algorithm: "aes-256-gcm",
|
|
1420
|
+
defaultKey: "",
|
|
1421
|
+
encryptEnv: false,
|
|
1422
|
+
encryptDatabase: false,
|
|
1423
|
+
encryptConfig: false,
|
|
1424
|
+
keyEnvVariable: "WEBARMOR_KEY"
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// src/core/Logger.ts
|
|
1429
|
+
var Logger = class {
|
|
1430
|
+
level;
|
|
1431
|
+
logs = [];
|
|
1432
|
+
maxLogs = 1e3;
|
|
1433
|
+
debugMode = false;
|
|
1434
|
+
levels = {
|
|
1435
|
+
error: 0,
|
|
1436
|
+
warn: 1,
|
|
1437
|
+
info: 2,
|
|
1438
|
+
debug: 3
|
|
1439
|
+
};
|
|
1440
|
+
constructor(options) {
|
|
1441
|
+
this.level = options?.level || "info";
|
|
1442
|
+
this.debugMode = options?.debug || false;
|
|
1443
|
+
}
|
|
1444
|
+
shouldLog(level) {
|
|
1445
|
+
return this.levels[level] <= this.levels[this.level];
|
|
1446
|
+
}
|
|
1447
|
+
formatMessage(level, message, data) {
|
|
1448
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1449
|
+
let formatted = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
|
1450
|
+
if (data && this.debugMode) {
|
|
1451
|
+
formatted += ` ${JSON.stringify(data)}`;
|
|
1452
|
+
}
|
|
1453
|
+
return formatted;
|
|
1454
|
+
}
|
|
1455
|
+
error(message, data) {
|
|
1456
|
+
if (this.shouldLog("error")) {
|
|
1457
|
+
console.error(this.formatMessage("error", message, data));
|
|
1458
|
+
this.addLog("error", message, data);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
warn(message, data) {
|
|
1462
|
+
if (this.shouldLog("warn")) {
|
|
1463
|
+
console.warn(this.formatMessage("warn", message, data));
|
|
1464
|
+
this.addLog("warn", message, data);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
info(message, data) {
|
|
1468
|
+
if (this.shouldLog("info")) {
|
|
1469
|
+
console.log(this.formatMessage("info", message, data));
|
|
1470
|
+
this.addLog("info", message, data);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
debug(message, data) {
|
|
1474
|
+
if (this.shouldLog("debug")) {
|
|
1475
|
+
console.log(this.formatMessage("debug", message, data));
|
|
1476
|
+
this.addLog("debug", message, data);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
addLog(level, message, data) {
|
|
1480
|
+
this.logs.push({
|
|
1481
|
+
level,
|
|
1482
|
+
message,
|
|
1483
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1484
|
+
data
|
|
1485
|
+
});
|
|
1486
|
+
if (this.logs.length > this.maxLogs) {
|
|
1487
|
+
this.logs.shift();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
getLogs(level) {
|
|
1491
|
+
if (level) {
|
|
1492
|
+
return this.logs.filter((log) => log.level === level);
|
|
1493
|
+
}
|
|
1494
|
+
return [...this.logs];
|
|
1495
|
+
}
|
|
1496
|
+
clearLogs() {
|
|
1497
|
+
this.logs = [];
|
|
1498
|
+
}
|
|
1499
|
+
setLevel(level) {
|
|
1500
|
+
this.level = level;
|
|
1501
|
+
}
|
|
1502
|
+
setDebug(debug) {
|
|
1503
|
+
this.debugMode = debug;
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
var logger = new Logger();
|
|
1507
|
+
var Logger_default = Logger;
|
|
1508
|
+
|
|
1509
|
+
// src/core/Shield.ts
|
|
1510
|
+
var Shield = class {
|
|
1511
|
+
config;
|
|
1512
|
+
logger;
|
|
1513
|
+
stats = {
|
|
1514
|
+
requests: 0,
|
|
1515
|
+
blocked: 0,
|
|
1516
|
+
allowed: 0,
|
|
1517
|
+
ddosBlocked: 0,
|
|
1518
|
+
rateLimited: 0,
|
|
1519
|
+
xssBlocked: 0,
|
|
1520
|
+
sqlInjectionBlocked: 0,
|
|
1521
|
+
botBlocked: 0,
|
|
1522
|
+
ipBlocked: 0,
|
|
1523
|
+
fileBlocked: 0,
|
|
1524
|
+
startTime: Date.now()
|
|
1525
|
+
};
|
|
1526
|
+
modules = /* @__PURE__ */ new Map();
|
|
1527
|
+
enabledModules = /* @__PURE__ */ new Set();
|
|
1528
|
+
constructor(userConfig) {
|
|
1529
|
+
this.config = { ...defaultConfig, ...userConfig };
|
|
1530
|
+
this.logger = new Logger_default({
|
|
1531
|
+
level: this.config.logLevel,
|
|
1532
|
+
debug: this.config.debug
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
async initialize() {
|
|
1536
|
+
this.logger.info("WebArmor initializing...", { framework: this.config.framework });
|
|
1537
|
+
await this.initializeModules();
|
|
1538
|
+
this.logger.info("WebArmor initialized successfully");
|
|
1539
|
+
}
|
|
1540
|
+
async initializeModules() {
|
|
1541
|
+
if (this.config.ddos.enabled) {
|
|
1542
|
+
try {
|
|
1543
|
+
const { DDoSProtection: DDoSProtection2 } = await Promise.resolve().then(() => (init_DDoSProtection(), DDoSProtection_exports));
|
|
1544
|
+
const ddos = new DDoSProtection2(this.config.ddos, this.logger);
|
|
1545
|
+
this.modules.set("ddos", ddos);
|
|
1546
|
+
this.enabledModules.add("ddos");
|
|
1547
|
+
} catch (e) {
|
|
1548
|
+
this.logger.error("Failed to initialize DDoS protection", e);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (this.config.rateLimit.enabled) {
|
|
1552
|
+
try {
|
|
1553
|
+
const { RateLimiter: RateLimiter2 } = await Promise.resolve().then(() => (init_RateLimiter(), RateLimiter_exports));
|
|
1554
|
+
const rateLimit = new RateLimiter2(this.config.rateLimit, this.logger);
|
|
1555
|
+
this.modules.set("rateLimit", rateLimit);
|
|
1556
|
+
this.enabledModules.add("rateLimit");
|
|
1557
|
+
} catch (e) {
|
|
1558
|
+
this.logger.error("Failed to initialize Rate Limiter", e);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if (this.config.securityHeaders && typeof this.config.securityHeaders !== "boolean" && this.config.securityHeaders.enabled) {
|
|
1562
|
+
try {
|
|
1563
|
+
const { SecurityHeaders: SecurityHeaders2 } = await Promise.resolve().then(() => (init_SecurityHeaders(), SecurityHeaders_exports));
|
|
1564
|
+
const headers = new SecurityHeaders2(this.config.securityHeaders, this.logger);
|
|
1565
|
+
this.modules.set("securityHeaders", headers);
|
|
1566
|
+
this.enabledModules.add("securityHeaders");
|
|
1567
|
+
} catch (e) {
|
|
1568
|
+
this.logger.error("Failed to initialize Security Headers", e);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (this.config.cors.enabled) {
|
|
1572
|
+
try {
|
|
1573
|
+
const { CORSManager: CORSManager2 } = await Promise.resolve().then(() => (init_CORSManager(), CORSManager_exports));
|
|
1574
|
+
const cors = new CORSManager2(this.config.cors, this.logger);
|
|
1575
|
+
this.modules.set("cors", cors);
|
|
1576
|
+
this.enabledModules.add("cors");
|
|
1577
|
+
} catch (e) {
|
|
1578
|
+
this.logger.error("Failed to initialize CORS", e);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
if (this.config.xss.enabled) {
|
|
1582
|
+
try {
|
|
1583
|
+
const { XSSProtection: XSSProtection2 } = await Promise.resolve().then(() => (init_XSSProtection(), XSSProtection_exports));
|
|
1584
|
+
const xss = new XSSProtection2(this.config.xss, this.logger);
|
|
1585
|
+
this.modules.set("xss", xss);
|
|
1586
|
+
this.enabledModules.add("xss");
|
|
1587
|
+
} catch (e) {
|
|
1588
|
+
this.logger.error("Failed to initialize XSS Protection", e);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
if (this.config.csrf.enabled) {
|
|
1592
|
+
try {
|
|
1593
|
+
const { CSRFProtection: CSRFProtection2 } = await Promise.resolve().then(() => (init_CSRFProtection(), CSRFProtection_exports));
|
|
1594
|
+
const csrf = new CSRFProtection2(this.config.csrf, this.logger);
|
|
1595
|
+
this.modules.set("csrf", csrf);
|
|
1596
|
+
this.enabledModules.add("csrf");
|
|
1597
|
+
} catch (e) {
|
|
1598
|
+
this.logger.error("Failed to initialize CSRF Protection", e);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
if (this.config.sqlInjection.enabled) {
|
|
1602
|
+
try {
|
|
1603
|
+
const { SQLInjectionProtection: SQLInjectionProtection2 } = await Promise.resolve().then(() => (init_SQLInjectionProtection(), SQLInjectionProtection_exports));
|
|
1604
|
+
const sql = new SQLInjectionProtection2(this.config.sqlInjection, this.logger);
|
|
1605
|
+
this.modules.set("sqlInjection", sql);
|
|
1606
|
+
this.enabledModules.add("sqlInjection");
|
|
1607
|
+
} catch (e) {
|
|
1608
|
+
this.logger.error("Failed to initialize SQL Injection Protection", e);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
if (this.config.botProtection.enabled) {
|
|
1612
|
+
try {
|
|
1613
|
+
const { BotProtection: BotProtection2 } = await Promise.resolve().then(() => (init_BotProtection(), BotProtection_exports));
|
|
1614
|
+
const bot = new BotProtection2(this.config.botProtection, this.logger);
|
|
1615
|
+
this.modules.set("botProtection", bot);
|
|
1616
|
+
this.enabledModules.add("botProtection");
|
|
1617
|
+
} catch (e) {
|
|
1618
|
+
this.logger.error("Failed to initialize Bot Protection", e);
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
if (this.config.ipFilter.enabled) {
|
|
1622
|
+
try {
|
|
1623
|
+
const { IPFilter: IPFilter2 } = await Promise.resolve().then(() => (init_IPFilter(), IPFilter_exports));
|
|
1624
|
+
const ipFilter = new IPFilter2(this.config.ipFilter, this.logger);
|
|
1625
|
+
this.modules.set("ipFilter", ipFilter);
|
|
1626
|
+
this.enabledModules.add("ipFilter");
|
|
1627
|
+
} catch (e) {
|
|
1628
|
+
this.logger.error("Failed to initialize IP Filter", e);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
if (this.config.fileBlock.enabled) {
|
|
1632
|
+
try {
|
|
1633
|
+
const { FileBlocker: FileBlocker2 } = await Promise.resolve().then(() => (init_FileBlocker(), FileBlocker_exports));
|
|
1634
|
+
const fileBlock = new FileBlocker2(this.config.fileBlock, this.logger);
|
|
1635
|
+
this.modules.set("fileBlock", fileBlock);
|
|
1636
|
+
this.enabledModules.add("fileBlock");
|
|
1637
|
+
} catch (e) {
|
|
1638
|
+
this.logger.error("Failed to initialize File Blocker", e);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
apply(app) {
|
|
1643
|
+
this.logger.info("Applying WebArmor middleware to app");
|
|
1644
|
+
if (app && typeof app.use === "function") {
|
|
1645
|
+
if (this.modules.has("securityHeaders")) {
|
|
1646
|
+
const headers = this.modules.get("securityHeaders");
|
|
1647
|
+
app.use(headers.middleware());
|
|
1648
|
+
}
|
|
1649
|
+
if (this.modules.has("cors")) {
|
|
1650
|
+
const cors = this.modules.get("cors");
|
|
1651
|
+
app.use(cors.middleware());
|
|
1652
|
+
}
|
|
1653
|
+
if (this.modules.has("rateLimit")) {
|
|
1654
|
+
const rateLimit = this.modules.get("rateLimit");
|
|
1655
|
+
app.use(rateLimit.middleware());
|
|
1656
|
+
}
|
|
1657
|
+
if (this.modules.has("xss")) {
|
|
1658
|
+
const xss = this.modules.get("xss");
|
|
1659
|
+
app.use(xss.middleware());
|
|
1660
|
+
}
|
|
1661
|
+
if (this.modules.has("sqlInjection")) {
|
|
1662
|
+
const sql = this.modules.get("sqlInjection");
|
|
1663
|
+
app.use(sql.middleware());
|
|
1664
|
+
}
|
|
1665
|
+
if (this.modules.has("botProtection")) {
|
|
1666
|
+
const bot = this.modules.get("botProtection");
|
|
1667
|
+
app.use(bot.middleware());
|
|
1668
|
+
}
|
|
1669
|
+
if (this.modules.has("ipFilter")) {
|
|
1670
|
+
const ipFilter = this.modules.get("ipFilter");
|
|
1671
|
+
app.use(ipFilter.middleware());
|
|
1672
|
+
}
|
|
1673
|
+
if (this.modules.has("fileBlock")) {
|
|
1674
|
+
const fileBlock = this.modules.get("fileBlock");
|
|
1675
|
+
app.use(fileBlock.middleware());
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
this.logger.info("WebArmor middleware applied");
|
|
1679
|
+
}
|
|
1680
|
+
async protect(data) {
|
|
1681
|
+
this.stats.requests++;
|
|
1682
|
+
if (this.modules.has("ipFilter")) {
|
|
1683
|
+
const ipFilter = this.modules.get("ipFilter");
|
|
1684
|
+
const result = await ipFilter.check(data.ip);
|
|
1685
|
+
if (!result.allowed) {
|
|
1686
|
+
this.stats.ipBlocked++;
|
|
1687
|
+
this.stats.blocked++;
|
|
1688
|
+
return result;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (this.modules.has("ddos")) {
|
|
1692
|
+
const ddos = this.modules.get("ddos");
|
|
1693
|
+
const result = await ddos.check(data);
|
|
1694
|
+
if (!result.allowed) {
|
|
1695
|
+
this.stats.ddosBlocked++;
|
|
1696
|
+
this.stats.blocked++;
|
|
1697
|
+
return result;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (this.modules.has("rateLimit")) {
|
|
1701
|
+
const rateLimit = this.modules.get("rateLimit");
|
|
1702
|
+
const key = data.ip;
|
|
1703
|
+
const result = await rateLimit.check(key);
|
|
1704
|
+
if (!result.allowed) {
|
|
1705
|
+
this.stats.rateLimited++;
|
|
1706
|
+
this.stats.blocked++;
|
|
1707
|
+
return result;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (this.modules.has("botProtection")) {
|
|
1711
|
+
const bot = this.modules.get("botProtection");
|
|
1712
|
+
const result = await bot.check(data);
|
|
1713
|
+
if (!result.allowed) {
|
|
1714
|
+
this.stats.botBlocked++;
|
|
1715
|
+
this.stats.blocked++;
|
|
1716
|
+
return result;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (this.modules.has("xss") && data.body) {
|
|
1720
|
+
const xss = this.modules.get("xss");
|
|
1721
|
+
const result = xss.check(data.body);
|
|
1722
|
+
if (!result.allowed) {
|
|
1723
|
+
this.stats.xssBlocked++;
|
|
1724
|
+
if (this.config.xss.blockOnDetect) {
|
|
1725
|
+
this.stats.blocked++;
|
|
1726
|
+
return result;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (this.modules.has("sqlInjection")) {
|
|
1731
|
+
const sql = this.modules.get("sqlInjection");
|
|
1732
|
+
const result = sql.check(data);
|
|
1733
|
+
if (!result.allowed) {
|
|
1734
|
+
this.stats.sqlInjectionBlocked++;
|
|
1735
|
+
if (this.config.sqlInjection.blockOnDetect) {
|
|
1736
|
+
this.stats.blocked++;
|
|
1737
|
+
return result;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
this.stats.allowed++;
|
|
1742
|
+
return { allowed: true };
|
|
1743
|
+
}
|
|
1744
|
+
getStats() {
|
|
1745
|
+
return {
|
|
1746
|
+
...this.stats,
|
|
1747
|
+
uptime: Date.now() - this.stats.startTime,
|
|
1748
|
+
enabledModules: Array.from(this.enabledModules)
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
updateConfig(config) {
|
|
1752
|
+
this.config = { ...this.config, ...config };
|
|
1753
|
+
this.logger.info("Configuration updated");
|
|
1754
|
+
}
|
|
1755
|
+
enableModule(moduleName) {
|
|
1756
|
+
if (this.modules.has(moduleName)) {
|
|
1757
|
+
this.enabledModules.add(moduleName);
|
|
1758
|
+
this.logger.info(`Module ${moduleName} enabled`);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
disableModule(moduleName) {
|
|
1762
|
+
if (this.enabledModules.has(moduleName)) {
|
|
1763
|
+
this.enabledModules.delete(moduleName);
|
|
1764
|
+
this.logger.info(`Module ${moduleName} disabled`);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
getLogger() {
|
|
1768
|
+
return this.logger;
|
|
1769
|
+
}
|
|
1770
|
+
getConfig() {
|
|
1771
|
+
return this.config;
|
|
1772
|
+
}
|
|
1773
|
+
getModule(name) {
|
|
1774
|
+
return this.modules.get(name);
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
var Shield_default = Shield;
|
|
1778
|
+
|
|
1779
|
+
// src/encryption/FileEncryptor.ts
|
|
1780
|
+
var import_crypto2 = __toESM(require("crypto"), 1);
|
|
1781
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
1782
|
+
var FileEncryptor = class {
|
|
1783
|
+
algorithm = "aes-256-gcm";
|
|
1784
|
+
keyLength = 32;
|
|
1785
|
+
ivLength = 16;
|
|
1786
|
+
authTagLength = 16;
|
|
1787
|
+
deriveKey(password, salt) {
|
|
1788
|
+
return import_crypto2.default.pbkdf2Sync(password, salt, 1e5, this.keyLength, "sha256");
|
|
1789
|
+
}
|
|
1790
|
+
encryptFile(inputPath, outputPath, password) {
|
|
1791
|
+
return new Promise((resolve, reject) => {
|
|
1792
|
+
try {
|
|
1793
|
+
const salt = import_crypto2.default.randomBytes(32);
|
|
1794
|
+
const key = this.deriveKey(password, salt);
|
|
1795
|
+
const iv = import_crypto2.default.randomBytes(this.ivLength);
|
|
1796
|
+
const cipher = import_crypto2.default.createCipheriv(this.algorithm, key, iv);
|
|
1797
|
+
const input = import_fs.default.readFileSync(inputPath);
|
|
1798
|
+
const encrypted = Buffer.concat([cipher.update(input), cipher.final()]);
|
|
1799
|
+
const authTag = cipher.getAuthTag();
|
|
1800
|
+
const output = Buffer.concat([
|
|
1801
|
+
salt,
|
|
1802
|
+
iv,
|
|
1803
|
+
authTag,
|
|
1804
|
+
encrypted
|
|
1805
|
+
]);
|
|
1806
|
+
import_fs.default.writeFileSync(outputPath, output);
|
|
1807
|
+
resolve();
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
reject(error);
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
decryptFile(inputPath, outputPath, password) {
|
|
1814
|
+
return new Promise((resolve, reject) => {
|
|
1815
|
+
try {
|
|
1816
|
+
const fileData = import_fs.default.readFileSync(inputPath);
|
|
1817
|
+
const salt = fileData.subarray(0, 32);
|
|
1818
|
+
const iv = fileData.subarray(32, 32 + this.ivLength);
|
|
1819
|
+
const authTag = fileData.subarray(32 + this.ivLength, 32 + this.ivLength + this.authTagLength);
|
|
1820
|
+
const encrypted = fileData.subarray(32 + this.ivLength + this.authTagLength);
|
|
1821
|
+
const key = this.deriveKey(password, salt);
|
|
1822
|
+
const decipher = import_crypto2.default.createDecipheriv(this.algorithm, key, iv);
|
|
1823
|
+
decipher.setAuthTag(authTag);
|
|
1824
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
1825
|
+
import_fs.default.writeFileSync(outputPath, decrypted);
|
|
1826
|
+
resolve();
|
|
1827
|
+
} catch (error) {
|
|
1828
|
+
reject(error);
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
encryptString(data, password) {
|
|
1833
|
+
const salt = import_crypto2.default.randomBytes(32);
|
|
1834
|
+
const key = this.deriveKey(password, salt);
|
|
1835
|
+
const iv = import_crypto2.default.randomBytes(this.ivLength);
|
|
1836
|
+
const cipher = import_crypto2.default.createCipheriv(this.algorithm, key, iv);
|
|
1837
|
+
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
1838
|
+
const authTag = cipher.getAuthTag();
|
|
1839
|
+
return Buffer.concat([salt, iv, authTag, encrypted]).toString("base64");
|
|
1840
|
+
}
|
|
1841
|
+
decryptString(encryptedData, password) {
|
|
1842
|
+
const data = Buffer.from(encryptedData, "base64");
|
|
1843
|
+
const salt = data.subarray(0, 32);
|
|
1844
|
+
const iv = data.subarray(32, 32 + this.ivLength);
|
|
1845
|
+
const authTag = data.subarray(32 + this.ivLength, 32 + this.ivLength + this.authTagLength);
|
|
1846
|
+
const encrypted = data.subarray(32 + this.ivLength + this.authTagLength);
|
|
1847
|
+
const key = this.deriveKey(password, salt);
|
|
1848
|
+
const decipher = import_crypto2.default.createDecipheriv(this.algorithm, key, iv);
|
|
1849
|
+
decipher.setAuthTag(authTag);
|
|
1850
|
+
return decipher.update(encrypted) + decipher.final("utf8");
|
|
1851
|
+
}
|
|
1852
|
+
encryptJSON(data, password) {
|
|
1853
|
+
const jsonString = JSON.stringify(data);
|
|
1854
|
+
return this.encryptString(jsonString, password);
|
|
1855
|
+
}
|
|
1856
|
+
decryptJSON(encryptedData, password) {
|
|
1857
|
+
const decrypted = this.decryptString(encryptedData, password);
|
|
1858
|
+
return JSON.parse(decrypted);
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
var FileEncryptor_default = FileEncryptor;
|
|
1862
|
+
|
|
1863
|
+
// src/encryption/ConfigEncryptor.ts
|
|
1864
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
1865
|
+
var import_path = __toESM(require("path"), 1);
|
|
1866
|
+
var ConfigEncryptor = class {
|
|
1867
|
+
encryptor;
|
|
1868
|
+
configDir;
|
|
1869
|
+
backupDir;
|
|
1870
|
+
constructor(configDir = "./config") {
|
|
1871
|
+
this.encryptor = new FileEncryptor_default();
|
|
1872
|
+
this.configDir = configDir;
|
|
1873
|
+
this.backupDir = import_path.default.join(configDir, ".backup");
|
|
1874
|
+
}
|
|
1875
|
+
encryptConfigFile(filename, password) {
|
|
1876
|
+
const inputPath = import_path.default.join(this.configDir, filename);
|
|
1877
|
+
const outputPath = import_path.default.join(this.configDir, `${filename}.enc`);
|
|
1878
|
+
if (!import_fs2.default.existsSync(this.configDir)) {
|
|
1879
|
+
import_fs2.default.mkdirSync(this.configDir, { recursive: true });
|
|
1880
|
+
}
|
|
1881
|
+
this.createBackup(inputPath);
|
|
1882
|
+
this.encryptor.encryptFile(inputPath, outputPath, password);
|
|
1883
|
+
import_fs2.default.unlinkSync(inputPath);
|
|
1884
|
+
}
|
|
1885
|
+
decryptConfigFile(filename, password) {
|
|
1886
|
+
const inputPath = import_path.default.join(this.configDir, `${filename}.enc`);
|
|
1887
|
+
const outputPath = import_path.default.join(this.configDir, filename.replace(".enc", ""));
|
|
1888
|
+
if (!import_fs2.default.existsSync(inputPath)) {
|
|
1889
|
+
throw new Error(`Encrypted file not found: ${filename}`);
|
|
1890
|
+
}
|
|
1891
|
+
this.encryptor.decryptFile(inputPath, outputPath, password);
|
|
1892
|
+
}
|
|
1893
|
+
encryptEnvFile(password) {
|
|
1894
|
+
const envPath = import_path.default.join(process.cwd(), ".env");
|
|
1895
|
+
const envBackupPath = import_path.default.join(process.cwd(), ".env.backup");
|
|
1896
|
+
const envEncryptedPath = import_path.default.join(process.cwd(), ".env.enc");
|
|
1897
|
+
if (!import_fs2.default.existsSync(envPath)) {
|
|
1898
|
+
throw new Error(".env file not found");
|
|
1899
|
+
}
|
|
1900
|
+
if (import_fs2.default.existsSync(envBackupPath)) {
|
|
1901
|
+
import_fs2.default.unlinkSync(envBackupPath);
|
|
1902
|
+
}
|
|
1903
|
+
import_fs2.default.copyFileSync(envPath, envBackupPath);
|
|
1904
|
+
this.encryptor.encryptFile(envPath, envEncryptedPath, password);
|
|
1905
|
+
import_fs2.default.unlinkSync(envPath);
|
|
1906
|
+
}
|
|
1907
|
+
decryptEnvFile(password) {
|
|
1908
|
+
const envEncryptedPath = import_path.default.join(process.cwd(), ".env.enc");
|
|
1909
|
+
const envPath = import_path.default.join(process.cwd(), ".env");
|
|
1910
|
+
if (!import_fs2.default.existsSync(envEncryptedPath)) {
|
|
1911
|
+
throw new Error(".env.enc file not found");
|
|
1912
|
+
}
|
|
1913
|
+
this.encryptor.decryptFile(envEncryptedPath, envPath, password);
|
|
1914
|
+
}
|
|
1915
|
+
getEncryptedEnv(password) {
|
|
1916
|
+
const envEncryptedPath = import_path.default.join(process.cwd(), ".env.enc");
|
|
1917
|
+
if (!import_fs2.default.existsSync(envEncryptedPath)) {
|
|
1918
|
+
throw new Error(".env.enc file not found");
|
|
1919
|
+
}
|
|
1920
|
+
const tempOutput = import_path.default.join(process.cwd(), ".env.temp");
|
|
1921
|
+
this.encryptor.decryptFile(envEncryptedPath, tempOutput, password);
|
|
1922
|
+
const content = import_fs2.default.readFileSync(tempOutput, "utf8");
|
|
1923
|
+
import_fs2.default.unlinkSync(tempOutput);
|
|
1924
|
+
const envVars = {};
|
|
1925
|
+
const lines = content.split("\n");
|
|
1926
|
+
for (const line of lines) {
|
|
1927
|
+
const trimmed = line.trim();
|
|
1928
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1929
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
1930
|
+
if (key) {
|
|
1931
|
+
envVars[key.trim()] = valueParts.join("=").trim();
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
return envVars;
|
|
1935
|
+
}
|
|
1936
|
+
createBackup(filePath) {
|
|
1937
|
+
if (!import_fs2.default.existsSync(filePath)) return;
|
|
1938
|
+
if (!import_fs2.default.existsSync(this.backupDir)) {
|
|
1939
|
+
import_fs2.default.mkdirSync(this.backupDir, { recursive: true });
|
|
1940
|
+
}
|
|
1941
|
+
const filename = import_path.default.basename(filePath);
|
|
1942
|
+
const timestamp = Date.now();
|
|
1943
|
+
const backupPath = import_path.default.join(this.backupDir, `${filename}.${timestamp}.bak`);
|
|
1944
|
+
import_fs2.default.copyFileSync(filePath, backupPath);
|
|
1945
|
+
}
|
|
1946
|
+
restoreBackup(filename) {
|
|
1947
|
+
if (!import_fs2.default.existsSync(this.backupDir)) {
|
|
1948
|
+
throw new Error("No backups found");
|
|
1949
|
+
}
|
|
1950
|
+
const backups = import_fs2.default.readdirSync(this.backupDir).filter((f) => f.startsWith(filename)).sort().reverse();
|
|
1951
|
+
if (backups.length === 0) {
|
|
1952
|
+
throw new Error(`No backups found for ${filename}`);
|
|
1953
|
+
}
|
|
1954
|
+
const latestBackup = import_path.default.join(this.backupDir, backups[0]);
|
|
1955
|
+
const originalPath = import_path.default.join(this.configDir, filename);
|
|
1956
|
+
import_fs2.default.copyFileSync(latestBackup, originalPath);
|
|
1957
|
+
}
|
|
1958
|
+
listBackups() {
|
|
1959
|
+
if (!import_fs2.default.existsSync(this.backupDir)) {
|
|
1960
|
+
return [];
|
|
1961
|
+
}
|
|
1962
|
+
return import_fs2.default.readdirSync(this.backupDir);
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
var ConfigEncryptor_default = ConfigEncryptor;
|
|
1966
|
+
|
|
1967
|
+
// src/encryption/DatabaseEncryptor.ts
|
|
1968
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
1969
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
1970
|
+
var DatabaseEncryptor = class {
|
|
1971
|
+
encryptor;
|
|
1972
|
+
dbDir;
|
|
1973
|
+
constructor(dbDir = "./data") {
|
|
1974
|
+
this.encryptor = new FileEncryptor_default();
|
|
1975
|
+
this.dbDir = dbDir;
|
|
1976
|
+
}
|
|
1977
|
+
encryptDatabase(dbPath, password, outputPath) {
|
|
1978
|
+
const targetPath = outputPath || `${dbPath}.enc`;
|
|
1979
|
+
if (!import_fs3.default.existsSync(dbPath)) {
|
|
1980
|
+
throw new Error(`Database file not found: ${dbPath}`);
|
|
1981
|
+
}
|
|
1982
|
+
this.encryptor.encryptFile(dbPath, targetPath, password);
|
|
1983
|
+
}
|
|
1984
|
+
decryptDatabase(encryptedPath, password, outputPath) {
|
|
1985
|
+
const targetPath = outputPath || encryptedPath.replace(".enc", "");
|
|
1986
|
+
if (!import_fs3.default.existsSync(encryptedPath)) {
|
|
1987
|
+
throw new Error(`Encrypted database not found: ${encryptedPath}`);
|
|
1988
|
+
}
|
|
1989
|
+
this.encryptor.decryptFile(encryptedPath, targetPath, password);
|
|
1990
|
+
}
|
|
1991
|
+
encryptJSONDatabase(dbPath, password) {
|
|
1992
|
+
if (!import_fs3.default.existsSync(dbPath)) {
|
|
1993
|
+
throw new Error(`Database file not found: ${dbPath}`);
|
|
1994
|
+
}
|
|
1995
|
+
const data = JSON.parse(import_fs3.default.readFileSync(dbPath, "utf8"));
|
|
1996
|
+
const encrypted = this.encryptor.encryptJSON(data, password);
|
|
1997
|
+
import_fs3.default.writeFileSync(dbPath, encrypted, "utf8");
|
|
1998
|
+
}
|
|
1999
|
+
decryptJSONDatabase(dbPath, password) {
|
|
2000
|
+
if (!import_fs3.default.existsSync(dbPath)) {
|
|
2001
|
+
throw new Error(`Database file not found: ${dbPath}`);
|
|
2002
|
+
}
|
|
2003
|
+
const encrypted = import_fs3.default.readFileSync(dbPath, "utf8");
|
|
2004
|
+
return this.encryptor.decryptJSON(encrypted, password);
|
|
2005
|
+
}
|
|
2006
|
+
encryptSQLite(dbPath, password) {
|
|
2007
|
+
this.encryptDatabase(dbPath, password);
|
|
2008
|
+
}
|
|
2009
|
+
decryptSQLite(encryptedPath, password) {
|
|
2010
|
+
this.decryptDatabase(encryptedPath, password);
|
|
2011
|
+
}
|
|
2012
|
+
autoEncryptDatabases(password, extensions = [".json", ".sqlite", ".db"]) {
|
|
2013
|
+
if (!import_fs3.default.existsSync(this.dbDir)) {
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
const files = import_fs3.default.readdirSync(this.dbDir);
|
|
2017
|
+
for (const file of files) {
|
|
2018
|
+
const ext = import_path2.default.extname(file);
|
|
2019
|
+
if (extensions.includes(ext)) {
|
|
2020
|
+
const filePath = import_path2.default.join(this.dbDir, file);
|
|
2021
|
+
const encryptedPath = `${filePath}.enc`;
|
|
2022
|
+
this.encryptor.encryptFile(filePath, encryptedPath, password);
|
|
2023
|
+
import_fs3.default.unlinkSync(filePath);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
autoDecryptDatabases(password) {
|
|
2028
|
+
if (!import_fs3.default.existsSync(this.dbDir)) {
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
const files = import_fs3.default.readdirSync(this.dbDir);
|
|
2032
|
+
for (const file of files) {
|
|
2033
|
+
if (file.endsWith(".enc")) {
|
|
2034
|
+
const encryptedPath = import_path2.default.join(this.dbDir, file);
|
|
2035
|
+
const decryptedPath = encryptedPath.replace(".enc", "");
|
|
2036
|
+
this.encryptor.decryptFile(encryptedPath, decryptedPath, password);
|
|
2037
|
+
import_fs3.default.unlinkSync(encryptedPath);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
createEncryptedBackup(dbPath, backupDir, password) {
|
|
2042
|
+
if (!import_fs3.default.existsSync(backupDir)) {
|
|
2043
|
+
import_fs3.default.mkdirSync(backupDir, { recursive: true });
|
|
2044
|
+
}
|
|
2045
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2046
|
+
const filename = import_path2.default.basename(dbPath, import_path2.default.extname(dbPath));
|
|
2047
|
+
const backupPath = import_path2.default.join(backupDir, `${filename}_${timestamp}.enc`);
|
|
2048
|
+
this.encryptor.encryptFile(dbPath, backupPath, password);
|
|
2049
|
+
return backupPath;
|
|
2050
|
+
}
|
|
2051
|
+
listEncryptedBackups(backupDir) {
|
|
2052
|
+
if (!import_fs3.default.existsSync(backupDir)) {
|
|
2053
|
+
return [];
|
|
2054
|
+
}
|
|
2055
|
+
return import_fs3.default.readdirSync(backupDir).filter((f) => f.endsWith(".enc")).map((f) => import_path2.default.join(backupDir, f));
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
var DatabaseEncryptor_default = DatabaseEncryptor;
|
|
2059
|
+
|
|
2060
|
+
// src/encryption/SessionEncryptor.ts
|
|
2061
|
+
var import_crypto3 = __toESM(require("crypto"), 1);
|
|
2062
|
+
var SessionEncryptor = class {
|
|
2063
|
+
encryptor;
|
|
2064
|
+
key = null;
|
|
2065
|
+
constructor(key) {
|
|
2066
|
+
this.encryptor = new FileEncryptor_default();
|
|
2067
|
+
if (key) {
|
|
2068
|
+
this.setKey(key);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
setKey(key) {
|
|
2072
|
+
this.key = import_crypto3.default.createHash("sha256").update(key).digest();
|
|
2073
|
+
}
|
|
2074
|
+
ensureKey() {
|
|
2075
|
+
if (!this.key) {
|
|
2076
|
+
throw new Error("Encryption key not set. Call setKey() first.");
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
encryptSession(sessionData) {
|
|
2080
|
+
this.ensureKey();
|
|
2081
|
+
const json = JSON.stringify(sessionData);
|
|
2082
|
+
return this.encryptor.encryptString(json, this.key.toString("base64"));
|
|
2083
|
+
}
|
|
2084
|
+
decryptSession(encryptedData) {
|
|
2085
|
+
this.ensureKey();
|
|
2086
|
+
const decrypted = this.encryptor.decryptString(encryptedData, this.key.toString("base64"));
|
|
2087
|
+
return JSON.parse(decrypted);
|
|
2088
|
+
}
|
|
2089
|
+
encryptValue(value) {
|
|
2090
|
+
this.ensureKey();
|
|
2091
|
+
if (typeof value === "object") {
|
|
2092
|
+
return this.encryptor.encryptJSON(value, this.key.toString("base64"));
|
|
2093
|
+
}
|
|
2094
|
+
return this.encryptor.encryptString(String(value), this.key.toString("base64"));
|
|
2095
|
+
}
|
|
2096
|
+
decryptValue(encryptedValue) {
|
|
2097
|
+
this.ensureKey();
|
|
2098
|
+
try {
|
|
2099
|
+
const decrypted = this.encryptor.decryptString(encryptedValue, this.key.toString("base64"));
|
|
2100
|
+
try {
|
|
2101
|
+
return JSON.parse(decrypted);
|
|
2102
|
+
} catch {
|
|
2103
|
+
return decrypted;
|
|
2104
|
+
}
|
|
2105
|
+
} catch {
|
|
2106
|
+
try {
|
|
2107
|
+
return this.encryptor.decryptJSON(encryptedValue, this.key.toString("base64"));
|
|
2108
|
+
} catch {
|
|
2109
|
+
throw new Error("Failed to decrypt value");
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
createSignedSession(sessionData, secret) {
|
|
2114
|
+
const encrypted = this.encryptSession(sessionData);
|
|
2115
|
+
const signature = import_crypto3.default.createHmac("sha256", secret).update(encrypted).digest("base64");
|
|
2116
|
+
return `${encrypted}.${signature}`;
|
|
2117
|
+
}
|
|
2118
|
+
verifySignedSession(signedSession, secret) {
|
|
2119
|
+
const parts = signedSession.split(".");
|
|
2120
|
+
if (parts.length !== 2) {
|
|
2121
|
+
return { valid: false };
|
|
2122
|
+
}
|
|
2123
|
+
const [encrypted, signature] = parts;
|
|
2124
|
+
const expectedSignature = import_crypto3.default.createHmac("sha256", secret).update(encrypted).digest("base64");
|
|
2125
|
+
if (signature !== expectedSignature) {
|
|
2126
|
+
return { valid: false };
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
const data = this.decryptSession(encrypted);
|
|
2130
|
+
return { valid: true, data };
|
|
2131
|
+
} catch {
|
|
2132
|
+
return { valid: false };
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
encryptCookie(sessionData, password) {
|
|
2136
|
+
return this.encryptor.encryptString(JSON.stringify(sessionData), password);
|
|
2137
|
+
}
|
|
2138
|
+
decryptCookie(encryptedCookie, password) {
|
|
2139
|
+
const decrypted = this.encryptor.decryptString(encryptedCookie, password);
|
|
2140
|
+
return JSON.parse(decrypted);
|
|
2141
|
+
}
|
|
2142
|
+
generateSecureToken(length = 32) {
|
|
2143
|
+
return import_crypto3.default.randomBytes(length).toString("hex");
|
|
2144
|
+
}
|
|
2145
|
+
hashData(data) {
|
|
2146
|
+
return import_crypto3.default.createHash("sha256").update(data).digest("hex");
|
|
2147
|
+
}
|
|
2148
|
+
verifyHash(data, hash) {
|
|
2149
|
+
return this.hashData(data) === hash;
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
var SessionEncryptor_default = SessionEncryptor;
|
|
2153
|
+
|
|
2154
|
+
// src/index.ts
|
|
2155
|
+
async function createWebArmor(config) {
|
|
2156
|
+
const shield = new Shield_default(config);
|
|
2157
|
+
await shield.initialize();
|
|
2158
|
+
return shield;
|
|
2159
|
+
}
|
|
2160
|
+
var index_default = {
|
|
2161
|
+
create: createWebArmor,
|
|
2162
|
+
Shield: Shield_default,
|
|
2163
|
+
FileEncryptor: FileEncryptor_default,
|
|
2164
|
+
ConfigEncryptor: ConfigEncryptor_default,
|
|
2165
|
+
DatabaseEncryptor: DatabaseEncryptor_default,
|
|
2166
|
+
SessionEncryptor: SessionEncryptor_default,
|
|
2167
|
+
Logger: Logger_default
|
|
2168
|
+
};
|
|
2169
|
+
var security = {
|
|
2170
|
+
encrypt: (data, password) => {
|
|
2171
|
+
const enc = new FileEncryptor_default();
|
|
2172
|
+
return enc.encryptString(data, password);
|
|
2173
|
+
},
|
|
2174
|
+
decrypt: (encryptedData, password) => {
|
|
2175
|
+
const enc = new FileEncryptor_default();
|
|
2176
|
+
return enc.decryptString(encryptedData, password);
|
|
2177
|
+
},
|
|
2178
|
+
encryptFile: async (inputPath, outputPath, password) => {
|
|
2179
|
+
const enc = new FileEncryptor_default();
|
|
2180
|
+
await enc.encryptFile(inputPath, outputPath, password);
|
|
2181
|
+
},
|
|
2182
|
+
decryptFile: async (inputPath, outputPath, password) => {
|
|
2183
|
+
const enc = new FileEncryptor_default();
|
|
2184
|
+
await enc.decryptFile(inputPath, outputPath, password);
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
var createShield = createWebArmor;
|
|
2188
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2189
|
+
0 && (module.exports = {
|
|
2190
|
+
ConfigEncryptor,
|
|
2191
|
+
DatabaseEncryptor,
|
|
2192
|
+
FileEncryptor,
|
|
2193
|
+
Logger,
|
|
2194
|
+
SessionEncryptor,
|
|
2195
|
+
Shield,
|
|
2196
|
+
createShield,
|
|
2197
|
+
security
|
|
2198
|
+
});
|
|
2199
|
+
//# sourceMappingURL=index.cjs.map
|