auto-smart-security 1.0.15 → 1.0.16
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/README.md +48 -21
- package/dist/apply-security.js +11 -18
- package/dist/bot-detector.d.ts +1 -1
- package/dist/bot-detector.js +2 -2
- package/dist/rate-limiter.d.ts +2 -4
- package/dist/rate-limiter.js +13 -7
- package/dist/trust-engine.d.ts +2 -0
- package/dist/trust-engine.js +34 -0
- package/dist/types.d.ts +22 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,11 @@ import { SEND_NOTIFICATION_ERR } from './notify';
|
|
|
40
40
|
applySecurity(app, {
|
|
41
41
|
mode: 'prod',
|
|
42
42
|
pathWhitelist: ['admin/', 'media', 'oauth2'],
|
|
43
|
-
rateLimit: {
|
|
43
|
+
rateLimit: {
|
|
44
|
+
default: { max: 100, windowMs: 5000 },
|
|
45
|
+
trusted: { max: 500, windowMs: 5000 },
|
|
46
|
+
internal: { max: 2000, windowMs: 5000 },
|
|
47
|
+
},
|
|
44
48
|
trustProxy: 1
|
|
45
49
|
bot: { enabled: true },
|
|
46
50
|
blacklistTTL: 10 * 60 * 1000,
|
|
@@ -61,39 +65,35 @@ UA: ${info.ua ?? 'N/A'}`,
|
|
|
61
65
|
|
|
62
66
|
```ts
|
|
63
67
|
interface SecurityOptions {
|
|
64
|
-
/**
|
|
68
|
+
/** dev → skip security */
|
|
65
69
|
mode?: 'prod' | 'dev';
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
trust?: TrustOptions;
|
|
72
|
+
|
|
73
|
+
/** express trust proxy setting */
|
|
74
|
+
trustProxy?: number;
|
|
75
|
+
|
|
76
|
+
/** allow only these paths */
|
|
68
77
|
pathWhitelist: string[];
|
|
69
78
|
|
|
70
|
-
/**
|
|
79
|
+
/** hard blacklist */
|
|
71
80
|
staticBlacklist?: string[];
|
|
72
81
|
|
|
73
|
-
/**
|
|
82
|
+
/** dynamic blacklist ttl (ms) */
|
|
74
83
|
blacklistTTL?: number;
|
|
75
84
|
|
|
76
|
-
/**
|
|
85
|
+
/** custom blacklist store (Redis / Memory) */
|
|
77
86
|
blacklist?: {
|
|
78
87
|
store?: BlacklistStore;
|
|
79
88
|
};
|
|
80
89
|
|
|
81
|
-
/**
|
|
82
|
-
rateLimit?:
|
|
83
|
-
windowMs: number;
|
|
84
|
-
max: number;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/** express trust proxy setting */
|
|
88
|
-
trustProxy?: number;
|
|
90
|
+
/** rate limit */
|
|
91
|
+
rateLimit?: AdaptiveRateLimit;
|
|
89
92
|
|
|
90
|
-
/**
|
|
91
|
-
bot?:
|
|
92
|
-
enabled: boolean;
|
|
93
|
-
scoreLimit?: number;
|
|
94
|
-
};
|
|
93
|
+
/** bot detection */
|
|
94
|
+
bot?: BotOptions;
|
|
95
95
|
|
|
96
|
-
/**
|
|
96
|
+
/** notify app when blocked */
|
|
97
97
|
onBlock?: (info: BlockInfo) => void;
|
|
98
98
|
}
|
|
99
99
|
```
|
|
@@ -145,7 +145,11 @@ const redis = new Redis({
|
|
|
145
145
|
|
|
146
146
|
applySecurity(app, {
|
|
147
147
|
mode: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
|
|
148
|
-
rateLimit: {
|
|
148
|
+
rateLimit: {
|
|
149
|
+
default: { max: 100, windowMs: 5000 },
|
|
150
|
+
trusted: { max: 500, windowMs: 5000 },
|
|
151
|
+
internal: { max: 2000, windowMs: 5000 },
|
|
152
|
+
},
|
|
149
153
|
trustProxy: 1,
|
|
150
154
|
bot: { enabled: true },
|
|
151
155
|
blacklistTTL: 10 * 60 * 1000,
|
|
@@ -180,6 +184,29 @@ applySecurity(app, {
|
|
|
180
184
|
});
|
|
181
185
|
```
|
|
182
186
|
|
|
187
|
+
## Trust Ip/paths/paths
|
|
188
|
+
```ts
|
|
189
|
+
applySecurity(app, {
|
|
190
|
+
mode: 'prod',
|
|
191
|
+
trustProxy: 1,
|
|
192
|
+
|
|
193
|
+
trust: {
|
|
194
|
+
ips: ['113.xxx.xxx.xxx'],
|
|
195
|
+
origins: ['https://flamingo-fe.skydemo.vn'],
|
|
196
|
+
paths: ['admin'],
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
rateLimit: {
|
|
200
|
+
default: { max: 100, windowMs: 5000 },
|
|
201
|
+
trusted: { max: 500, windowMs: 5000 },
|
|
202
|
+
internal: { max: 2000, windowMs: 5000 },
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
bot: { enabled: true },
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
|
|
183
210
|
## 🧠 Best Practices
|
|
184
211
|
|
|
185
212
|
- ✔ Whitelist only valid API paths
|
package/dist/apply-security.js
CHANGED
|
@@ -7,8 +7,9 @@ exports.applySecurity = applySecurity;
|
|
|
7
7
|
const helmet_1 = __importDefault(require("helmet"));
|
|
8
8
|
const utils_1 = require("./utils");
|
|
9
9
|
const bot_detector_1 = require("./bot-detector");
|
|
10
|
-
const rate_limiter_1 = require("./rate-limiter");
|
|
11
10
|
const memory_store_1 = require("./blacklist/memory.store");
|
|
11
|
+
const trust_engine_1 = require("./trust-engine");
|
|
12
|
+
const rate_limiter_1 = require("./rate-limiter");
|
|
12
13
|
const STATIC_EXTENSIONS = [
|
|
13
14
|
'.png',
|
|
14
15
|
'.jpg',
|
|
@@ -51,19 +52,6 @@ function applySecurity(app, options) {
|
|
|
51
52
|
const botDetector = options.bot?.enabled === true
|
|
52
53
|
? new bot_detector_1.BotDetector(options.bot.scoreLimit)
|
|
53
54
|
: null;
|
|
54
|
-
/** ================= RATE LIMIT ================= */
|
|
55
|
-
if (options.rateLimit) {
|
|
56
|
-
app.use((0, rate_limiter_1.createRateLimiter)(options.rateLimit, (req) => {
|
|
57
|
-
const ip = (0, utils_1.getClientIP)(req);
|
|
58
|
-
blacklist.block(ip);
|
|
59
|
-
options.onBlock?.({
|
|
60
|
-
ip,
|
|
61
|
-
reason: 'rate-limit',
|
|
62
|
-
url: req.originalUrl,
|
|
63
|
-
ua: req.headers?.['user-agent'],
|
|
64
|
-
});
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
55
|
/** ================= IMAGES ================= */
|
|
68
56
|
const isStaticAsset = (url) => {
|
|
69
57
|
const path = url.split('?')[0].toLowerCase();
|
|
@@ -71,11 +59,16 @@ function applySecurity(app, options) {
|
|
|
71
59
|
};
|
|
72
60
|
/** ================= MAIN SECURITY ================= */
|
|
73
61
|
app.use(async (req, res, next) => {
|
|
62
|
+
const ip = (0, utils_1.getClientIP)(req);
|
|
63
|
+
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
|
+
}
|
|
74
69
|
// pass OPTIONS requests
|
|
75
70
|
if (req.method === 'OPTIONS')
|
|
76
71
|
return next();
|
|
77
|
-
const ip = (0, utils_1.getClientIP)(req);
|
|
78
|
-
const url = req.originalUrl;
|
|
79
72
|
if (isStaticAsset(url))
|
|
80
73
|
return next();
|
|
81
74
|
/** 1️⃣ Blacklist */
|
|
@@ -83,8 +76,8 @@ function applySecurity(app, options) {
|
|
|
83
76
|
return res.status(403).send('Access denied');
|
|
84
77
|
}
|
|
85
78
|
/** 2️⃣ Bot detection */
|
|
86
|
-
if (botDetector?.detect(req)) {
|
|
87
|
-
await blacklist.block(ip);
|
|
79
|
+
if (botDetector?.detect(req, trustLevel)) {
|
|
80
|
+
await blacklist.block(ip, options.blacklistTTL);
|
|
88
81
|
options.onBlock?.({
|
|
89
82
|
ip,
|
|
90
83
|
reason: 'bot-detected',
|
package/dist/bot-detector.d.ts
CHANGED
package/dist/bot-detector.js
CHANGED
|
@@ -8,8 +8,8 @@ class BotDetector {
|
|
|
8
8
|
this.scores = new Map();
|
|
9
9
|
this.ttl = 60000; // 1 minute
|
|
10
10
|
}
|
|
11
|
-
detect(req) {
|
|
12
|
-
if (req.method === 'OPTIONS')
|
|
11
|
+
detect(req, trustLevel = 0) {
|
|
12
|
+
if (req.method === 'OPTIONS' || trustLevel >= 5)
|
|
13
13
|
return false;
|
|
14
14
|
let score = 0;
|
|
15
15
|
const ua = (req.headers?.['user-agent'] || '').toLowerCase();
|
package/dist/rate-limiter.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
max: number;
|
|
4
|
-
}, onLimit: (req: any, res: any) => void): import("express-rate-limit").RateLimitRequestHandler;
|
|
1
|
+
import { AdaptiveRateLimit } from './types';
|
|
2
|
+
export declare function createAdaptiveRateLimiter(options: AdaptiveRateLimit, getKey: (req: any) => string, getLevel: (req: any) => number, onLimit: (req: any) => void): import("express-rate-limit").RateLimitRequestHandler;
|
package/dist/rate-limiter.js
CHANGED
|
@@ -3,16 +3,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.createAdaptiveRateLimiter = createAdaptiveRateLimiter;
|
|
7
7
|
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
8
|
-
function
|
|
8
|
+
function createAdaptiveRateLimiter(options, getKey, getLevel, onLimit) {
|
|
9
9
|
return (0, express_rate_limit_1.default)({
|
|
10
|
-
windowMs: options.windowMs,
|
|
11
|
-
max:
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
windowMs: options.default.windowMs,
|
|
11
|
+
max: (req) => {
|
|
12
|
+
const level = getLevel(req);
|
|
13
|
+
if (level >= 7 && options.internal)
|
|
14
|
+
return options.internal.max;
|
|
15
|
+
if (level >= 4 && options.trusted)
|
|
16
|
+
return options.trusted.max;
|
|
17
|
+
return options.default.max;
|
|
18
|
+
},
|
|
19
|
+
keyGenerator: getKey,
|
|
14
20
|
handler: (req, res) => {
|
|
15
|
-
onLimit(req
|
|
21
|
+
onLimit(req);
|
|
16
22
|
res.status(429).send('Too many requests');
|
|
17
23
|
},
|
|
18
24
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTrustLevel = getTrustLevel;
|
|
4
|
+
function getTrustLevel(req, ip, trust) {
|
|
5
|
+
let level = 0;
|
|
6
|
+
if (!trust)
|
|
7
|
+
return 0;
|
|
8
|
+
// IP trust
|
|
9
|
+
if (trust.ips) {
|
|
10
|
+
if (Array.isArray(trust.ips) ? trust.ips.includes(ip) : trust.ips(ip)) {
|
|
11
|
+
level += 5;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
// Origin trust
|
|
15
|
+
if (trust.origins?.length) {
|
|
16
|
+
const origin = req.headers.origin || '';
|
|
17
|
+
const referer = req.headers.referer || '';
|
|
18
|
+
if (trust.origins.some((o) => origin.startsWith(o) || referer.startsWith(o))) {
|
|
19
|
+
level += 3;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Path trust
|
|
23
|
+
if (trust.paths?.length) {
|
|
24
|
+
const path = req.originalUrl.split('?')[0].replace(/^\/+/, '');
|
|
25
|
+
if (trust.paths.some((p) => path.startsWith(p))) {
|
|
26
|
+
level += 2;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Auth header
|
|
30
|
+
if (req.headers.authorization) {
|
|
31
|
+
level += 2;
|
|
32
|
+
}
|
|
33
|
+
return level;
|
|
34
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,17 +7,34 @@ export interface BlockInfo {
|
|
|
7
7
|
ua?: string;
|
|
8
8
|
extra?: any;
|
|
9
9
|
}
|
|
10
|
-
export interface RateLimitOptions {
|
|
11
|
-
windowMs: number;
|
|
12
|
-
max: number;
|
|
13
|
-
}
|
|
14
10
|
export interface BotOptions {
|
|
15
11
|
enabled: boolean;
|
|
16
12
|
scoreLimit?: number;
|
|
17
13
|
}
|
|
14
|
+
export interface TrustOptions {
|
|
15
|
+
ips?: string[] | ((ip: string) => boolean);
|
|
16
|
+
origins?: string[];
|
|
17
|
+
paths?: string[];
|
|
18
|
+
minLevelToBypassBot?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface AdaptiveRateLimit {
|
|
21
|
+
default: {
|
|
22
|
+
max: number;
|
|
23
|
+
windowMs: number;
|
|
24
|
+
};
|
|
25
|
+
trusted?: {
|
|
26
|
+
max: number;
|
|
27
|
+
windowMs: number;
|
|
28
|
+
};
|
|
29
|
+
internal?: {
|
|
30
|
+
max: number;
|
|
31
|
+
windowMs: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
18
34
|
export interface SecurityOptions {
|
|
19
35
|
/** dev → skip security */
|
|
20
36
|
mode?: 'prod' | 'dev';
|
|
37
|
+
trust?: TrustOptions;
|
|
21
38
|
/** express trust proxy setting */
|
|
22
39
|
trustProxy?: number;
|
|
23
40
|
/** allow only these paths */
|
|
@@ -31,7 +48,7 @@ export interface SecurityOptions {
|
|
|
31
48
|
store?: BlacklistStore;
|
|
32
49
|
};
|
|
33
50
|
/** rate limit */
|
|
34
|
-
rateLimit?:
|
|
51
|
+
rateLimit?: AdaptiveRateLimit;
|
|
35
52
|
/** bot detection */
|
|
36
53
|
bot?: BotOptions;
|
|
37
54
|
/** notify app when blocked */
|