auto-smart-security 1.1.0 → 1.1.2

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.
@@ -40,11 +40,6 @@ function applySecurity(app, options) {
40
40
  }
41
41
  app.set('trust proxy', options.trustProxy);
42
42
  }
43
- /** ================= HELMET ================= */
44
- app.use((0, helmet_1.default)({
45
- crossOriginResourcePolicy: false, // 🔥
46
- crossOriginOpenerPolicy: false, // disable API
47
- }));
48
43
  /** ================= BLACKLIST STORE ================= */
49
44
  const blacklist = options.blacklist?.store ??
50
45
  new memory_store_1.MemoryBlacklistStore(options.staticBlacklist, options.blacklistTTL);
@@ -57,63 +52,69 @@ function applySecurity(app, options) {
57
52
  const path = url.split('?')[0].toLowerCase();
58
53
  return STATIC_EXTENSIONS.some((ext) => path.endsWith(ext));
59
54
  };
55
+ /** 3️⃣ Path whitelist */
56
+ const normalizePath = (url) => url.split('?')[0].replace(/^\/+/, '');
57
+ const isPathAllowed = (url, whitelist) => {
58
+ const path = normalizePath(url);
59
+ return whitelist.some((p) => path === p || path.includes(`${p}`));
60
+ };
61
+ function recordPathViolation(ip) {
62
+ const now = Date.now();
63
+ const entry = pathViolationMap.get(ip);
64
+ if (!entry || now - entry.ts > PATH_VIOLATION_TTL) {
65
+ pathViolationMap.set(ip, { count: 1, ts: now });
66
+ return false; // not block
67
+ }
68
+ entry.count++;
69
+ pathViolationMap.set(ip, entry);
70
+ return entry.count > PATH_VIOLATION_LIMIT; // block if limit exceeded
71
+ }
72
+ if (options.rateLimit) {
73
+ app.set('trust proxy', options.trustProxy);
74
+ // Lưu ý: createAdaptiveRateLimiter get IP/Trust
75
+ app.use((0, rate_limiter_1.createAdaptiveRateLimiter)(options.rateLimit, (req) => (0, utils_1.getClientIP)(req), (req) => (0, trust_engine_1.getTrustLevel)(req, (0, utils_1.getClientIP)(req), options.trust), (ip) => blacklist.block(ip)));
76
+ }
77
+ /** ================= HELMET ================= */
78
+ app.use((0, helmet_1.default)({
79
+ crossOriginResourcePolicy: false, // 🔥
80
+ crossOriginOpenerPolicy: false, // disable API
81
+ }));
60
82
  /** ================= MAIN SECURITY ================= */
61
83
  app.use(async (req, res, next) => {
62
84
  const ip = (0, utils_1.getClientIP)(req);
63
85
  const url = req.originalUrl;
64
- const trustLevel = (0, trust_engine_1.getTrustLevel)(req, ip, options.trust);
65
- /** ================= RATE LIMIT ================= */
66
- if (options.rateLimit) {
67
- app.use((0, rate_limiter_1.createAdaptiveRateLimiter)(options.rateLimit, (req) => (0, utils_1.getClientIP)(req), () => trustLevel, () => blacklist.block(ip)));
68
- }
69
- // pass OPTIONS requests
70
- if (req.method === 'OPTIONS')
86
+ if (req.method === 'OPTIONS' || isStaticAsset(url))
71
87
  return next();
88
+ const trustLevel = (0, trust_engine_1.getTrustLevel)(req, ip, options.trust);
89
+ const isWhitelisted = trustLevel >= 5;
90
+ const handleBlocking = async (reason) => {
91
+ options.onBlock?.({ ip, reason, url, ua: req.headers?.['user-agent'] });
92
+ if (isWhitelisted)
93
+ return false;
94
+ res.status(403).send(reason);
95
+ return true;
96
+ };
72
97
  if (isStaticAsset(url))
73
98
  return next();
74
99
  /** 1️⃣ Blacklist */
75
100
  if (await blacklist.isBlocked(ip)) {
76
- return res.status(403).send('Access denied');
101
+ if (await handleBlocking('blacklist'))
102
+ return;
77
103
  }
78
104
  /** 2️⃣ Bot detection */
79
105
  if (botDetector?.detect(req, trustLevel)) {
80
- await blacklist.block(ip, options.blacklistTTL);
81
- options.onBlock?.({
82
- ip,
83
- reason: 'bot-detected',
84
- url,
85
- ua: req.headers?.['user-agent'],
86
- });
87
- return res.status(403).send('Bot detected');
88
- }
89
- /** 3️⃣ Path whitelist */
90
- const normalizePath = (url) => url.split('?')[0].replace(/^\/+/, '');
91
- const isPathAllowed = (url, whitelist) => {
92
- const path = normalizePath(url);
93
- return whitelist.some((p) => path === p || path.includes(`${p}`));
94
- };
95
- function recordPathViolation(ip) {
96
- const now = Date.now();
97
- const entry = pathViolationMap.get(ip);
98
- if (!entry || now - entry.ts > PATH_VIOLATION_TTL) {
99
- pathViolationMap.set(ip, { count: 1, ts: now });
100
- return false; // not block
101
- }
102
- entry.count++;
103
- pathViolationMap.set(ip, entry);
104
- return entry.count > PATH_VIOLATION_LIMIT; // block if limit exceeded
106
+ if (!isWhitelisted)
107
+ await blacklist.block(ip, options.blacklistTTL);
108
+ if (await handleBlocking('bot-detected'))
109
+ return;
105
110
  }
106
111
  if (options.pathWhitelist?.length &&
107
112
  !isPathAllowed(url, options.pathWhitelist)) {
108
- const shouldBlock = recordPathViolation(ip); // check violation count
109
- if (shouldBlock) {
110
- await blacklist.block(ip);
111
- options.onBlock?.({
112
- ip,
113
- reason: 'path-not-allowed',
114
- url,
115
- });
116
- return res.status(403).send('Blocked path');
113
+ if (recordPathViolation(ip)) {
114
+ if (!isWhitelisted)
115
+ await blacklist.block(ip);
116
+ if (await handleBlocking('path-not-allowed'))
117
+ return;
117
118
  }
118
119
  }
119
120
  next();
@@ -8,8 +8,10 @@ class MemoryBlacklistStore {
8
8
  this.staticList = new Set(staticBlacklist);
9
9
  }
10
10
  isBlocked(ip) {
11
- if (this.staticList.has(ip))
11
+ if (this.staticList.has(ip)) {
12
+ console.log(`[DEBUG] IP ${ip} detect block in MEMORY store`);
12
13
  return true;
14
+ }
13
15
  const exp = this.dynamic.get(ip);
14
16
  if (!exp)
15
17
  return false;
@@ -22,6 +22,7 @@ class RedisBlacklistStore {
22
22
  async block(ip, ttlSeconds) {
23
23
  if (!this.redis)
24
24
  return;
25
+ console.log(`[DEBUG] IP ${ip} detect block in REDIS store`);
25
26
  const ttl = ttlSeconds ?? this.ttlSeconds;
26
27
  await this.redis.set(this.key(ip), '1', 'EX', ttl);
27
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-smart-security",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Production-ready security middleware for Express / NestJS",
5
5
  "author": "Hai Vinh <haivinhinspirit@gmail.com>",
6
6
  "main": "dist/index.js",