auto-smart-security 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/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/apply-security.d.ts +2 -0
- package/dist/apply-security.js +71 -0
- package/dist/blacklist/blacklist.interface.d.ts +4 -0
- package/dist/blacklist/blacklist.interface.js +2 -0
- package/dist/blacklist/memory.store.d.ts +9 -0
- package/dist/blacklist/memory.store.js +26 -0
- package/dist/blacklist/redis.store.d.ts +17 -0
- package/dist/blacklist/redis.store.js +25 -0
- package/dist/bot-detector.d.ts +6 -0
- package/dist/bot-detector.js +33 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/rate-limiter.d.ts +4 -0
- package/dist/rate-limiter.js +19 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +10 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 HaiVinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# auto-smart-security
|
|
2
|
+
|
|
3
|
+
π Production-ready security middleware for **Express / NestJS**
|
|
4
|
+
Protect your API from bots, abuse, and attacks with rate limiting, auto blacklist, and Redis support.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## β¨ Features
|
|
9
|
+
|
|
10
|
+
- β
Path whitelist (block API scanning)
|
|
11
|
+
- β
Rate limiting (express-rate-limit)
|
|
12
|
+
- β
Automatic IP blacklist with TTL
|
|
13
|
+
- β
Bot detection (score-based)
|
|
14
|
+
- β
Memory or Redis blacklist store
|
|
15
|
+
- β
Multi-instance / Docker / Kubernetes safe
|
|
16
|
+
- β
Framework-agnostic (Express & NestJS)
|
|
17
|
+
- β
`onBlock` hook for logging & notifications (Slack / Telegram / Sentryβ¦)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## π¦ Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install smart-security
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
β οΈ Peer dependency
|
|
28
|
+
Your application must have express installed
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install express
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## π Quick Start (Express / NestJS)
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { applySecurity } from 'smart-security';
|
|
38
|
+
import { SEND_NOTIFICATION_ERR } from './notify';
|
|
39
|
+
|
|
40
|
+
applySecurity(app, {
|
|
41
|
+
mode: 'prod',
|
|
42
|
+
pathWhitelist: ['admin/', 'media', 'oauth2'],
|
|
43
|
+
rateLimit: { max: 120, windowMs: 60_000 },
|
|
44
|
+
bot: { enabled: true },
|
|
45
|
+
blacklistTTL: 10 * 60 * 1000,
|
|
46
|
+
|
|
47
|
+
onBlock: (info) => {
|
|
48
|
+
SEND_NOTIFICATION_ERR(
|
|
49
|
+
`[SECURITY]
|
|
50
|
+
Reason: ${info.reason}
|
|
51
|
+
IP: ${info.ip}
|
|
52
|
+
URL: ${info.url}
|
|
53
|
+
UA: ${info.ua ?? 'N/A'}`
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## βοΈ SecurityOptions
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
interface SecurityOptions {
|
|
63
|
+
/** Skip all security when in dev mode */
|
|
64
|
+
mode?: 'prod' | 'dev';
|
|
65
|
+
|
|
66
|
+
/** Allowed API paths */
|
|
67
|
+
pathWhitelist: string[];
|
|
68
|
+
|
|
69
|
+
/** Hard-coded blacklist */
|
|
70
|
+
staticBlacklist?: string[];
|
|
71
|
+
|
|
72
|
+
/** Dynamic blacklist TTL (milliseconds) */
|
|
73
|
+
blacklistTTL?: number;
|
|
74
|
+
|
|
75
|
+
/** Custom blacklist store (Redis / Memory) */
|
|
76
|
+
blacklist?: {
|
|
77
|
+
store?: BlacklistStore;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Rate limiting */
|
|
81
|
+
rateLimit?: {
|
|
82
|
+
windowMs: number;
|
|
83
|
+
max: number;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Bot detection */
|
|
87
|
+
bot?: {
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
scoreLimit?: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Callback when a request is blocked */
|
|
93
|
+
onBlock?: (info: BlockInfo) => void;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## π« Block Reasons
|
|
98
|
+
|
|
99
|
+
The onBlock callback receives one of the following reasons:
|
|
100
|
+
|
|
101
|
+
| reason | Description |
|
|
102
|
+
| ------------------ | ------------------------ |
|
|
103
|
+
| `rate-limit` | Too many requests |
|
|
104
|
+
| `bot-detected` | Bot behavior detected |
|
|
105
|
+
| `path-not-allowed` | Path not in whitelist |
|
|
106
|
+
| `error-spam` | Repeated error responses |
|
|
107
|
+
| `blacklist` | IP is blacklisted |
|
|
108
|
+
|
|
109
|
+
## π€ Bot Detection
|
|
110
|
+
|
|
111
|
+
Bots are detected using a score-based system, including:
|
|
112
|
+
|
|
113
|
+
- Suspicious User-Agent (curl, python, scrapy)
|
|
114
|
+
- Missing browser headers
|
|
115
|
+
- No cookies
|
|
116
|
+
- Scanning sensitive paths (.env, wp-admin)
|
|
117
|
+
- Unusual HTTP methods
|
|
118
|
+
|
|
119
|
+
β‘ When the score exceeds the threshold β block + blacklist
|
|
120
|
+
|
|
121
|
+
## π§± Blacklist Store
|
|
122
|
+
|
|
123
|
+
π§ Memory (default)
|
|
124
|
+
|
|
125
|
+
- No configuration required
|
|
126
|
+
- Suitable for single-instance deployments
|
|
127
|
+
|
|
128
|
+
## βοΈ Redis (recommended for production)
|
|
129
|
+
|
|
130
|
+
Using ioredis
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import Redis from 'ioredis';
|
|
134
|
+
import { RedisBlacklistStore } from 'smart-security/dist/blacklist/redis.store';
|
|
135
|
+
|
|
136
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
137
|
+
|
|
138
|
+
applySecurity(app, {
|
|
139
|
+
pathWhitelist: ['admin/', 'media'],
|
|
140
|
+
blacklist: {
|
|
141
|
+
store: new RedisBlacklistStore(
|
|
142
|
+
redis,
|
|
143
|
+
['1.2.3.4'], // static blacklist
|
|
144
|
+
600 // TTL in seconds
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Development Mode
|
|
151
|
+
|
|
152
|
+
Disable all security logic (local development):
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
applySecurity(app, {
|
|
156
|
+
mode: 'dev',
|
|
157
|
+
pathWhitelist: [],
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## π§ Best Practices
|
|
162
|
+
|
|
163
|
+
- β Whitelist only valid API paths
|
|
164
|
+
- β Avoid permanent blacklisting
|
|
165
|
+
- β Use Redis for multi-instance environments
|
|
166
|
+
- β Handle logging & notifications via onBlock
|
|
167
|
+
|
|
168
|
+
## π§© Works With
|
|
169
|
+
|
|
170
|
+
- Express
|
|
171
|
+
- NestJS (Express adapter)
|
|
172
|
+
- Docker / Kubernetes
|
|
173
|
+
- Cloudflare / Nginx / reverse proxies
|
|
174
|
+
|
|
175
|
+
## π§© Works With
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applySecurity = applySecurity;
|
|
7
|
+
const helmet_1 = __importDefault(require("helmet"));
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
const bot_detector_1 = require("./bot-detector");
|
|
10
|
+
const rate_limiter_1 = require("./rate-limiter");
|
|
11
|
+
const memory_store_1 = require("./blacklist/memory.store");
|
|
12
|
+
function applySecurity(app, options) {
|
|
13
|
+
// Dev mode β skip security
|
|
14
|
+
if (options.mode === 'dev')
|
|
15
|
+
return;
|
|
16
|
+
app.set('trust proxy', true);
|
|
17
|
+
/** ================= BLACKLIST STORE ================= */
|
|
18
|
+
const blacklist = options.blacklist?.store ??
|
|
19
|
+
new memory_store_1.MemoryBlacklistStore(options.staticBlacklist, options.blacklistTTL);
|
|
20
|
+
/** ================= BOT DETECTOR ================= */
|
|
21
|
+
const botDetector = options.bot?.enabled === true
|
|
22
|
+
? new bot_detector_1.BotDetector(options.bot.scoreLimit)
|
|
23
|
+
: null;
|
|
24
|
+
/** ================= RATE LIMIT ================= */
|
|
25
|
+
if (options.rateLimit) {
|
|
26
|
+
app.use((0, rate_limiter_1.createRateLimiter)(options.rateLimit, (req) => {
|
|
27
|
+
const ip = (0, utils_1.getClientIP)(req);
|
|
28
|
+
blacklist.block(ip);
|
|
29
|
+
options.onBlock?.({
|
|
30
|
+
ip,
|
|
31
|
+
reason: 'rate-limit',
|
|
32
|
+
url: req.originalUrl,
|
|
33
|
+
ua: req.headers?.['user-agent'],
|
|
34
|
+
});
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/** ================= MAIN SECURITY ================= */
|
|
38
|
+
app.use(async (req, res, next) => {
|
|
39
|
+
const ip = (0, utils_1.getClientIP)(req);
|
|
40
|
+
const url = req.originalUrl;
|
|
41
|
+
/** 1οΈβ£ Blacklist */
|
|
42
|
+
if (await blacklist.isBlocked(ip)) {
|
|
43
|
+
return res.status(403).send('Access denied');
|
|
44
|
+
}
|
|
45
|
+
/** 2οΈβ£ Bot detection */
|
|
46
|
+
if (botDetector?.detect(req)) {
|
|
47
|
+
await blacklist.block(ip);
|
|
48
|
+
options.onBlock?.({
|
|
49
|
+
ip,
|
|
50
|
+
reason: 'bot-detected',
|
|
51
|
+
url,
|
|
52
|
+
ua: req.headers?.['user-agent'],
|
|
53
|
+
});
|
|
54
|
+
return res.status(403).send('Bot detected');
|
|
55
|
+
}
|
|
56
|
+
/** 3οΈβ£ Path whitelist */
|
|
57
|
+
if (options.pathWhitelist?.length &&
|
|
58
|
+
!options.pathWhitelist.some((p) => url.includes(p))) {
|
|
59
|
+
await blacklist.block(ip);
|
|
60
|
+
options.onBlock?.({
|
|
61
|
+
ip,
|
|
62
|
+
reason: 'path-not-allowed',
|
|
63
|
+
url,
|
|
64
|
+
});
|
|
65
|
+
return res.status(403).send('Blocked path');
|
|
66
|
+
}
|
|
67
|
+
next();
|
|
68
|
+
});
|
|
69
|
+
/** ================= HELMET ================= */
|
|
70
|
+
app.use((0, helmet_1.default)());
|
|
71
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BlacklistStore } from './blacklist.interface';
|
|
2
|
+
export declare class MemoryBlacklistStore implements BlacklistStore {
|
|
3
|
+
private ttl;
|
|
4
|
+
private dynamic;
|
|
5
|
+
private staticList;
|
|
6
|
+
constructor(staticBlacklist?: string[], ttl?: number);
|
|
7
|
+
isBlocked(ip: string): boolean;
|
|
8
|
+
block(ip: string, ttl?: number): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryBlacklistStore = void 0;
|
|
4
|
+
class MemoryBlacklistStore {
|
|
5
|
+
constructor(staticBlacklist = [], ttl = 10 * 60 * 1000) {
|
|
6
|
+
this.ttl = ttl;
|
|
7
|
+
this.dynamic = new Map();
|
|
8
|
+
this.staticList = new Set(staticBlacklist);
|
|
9
|
+
}
|
|
10
|
+
isBlocked(ip) {
|
|
11
|
+
if (this.staticList.has(ip))
|
|
12
|
+
return true;
|
|
13
|
+
const exp = this.dynamic.get(ip);
|
|
14
|
+
if (!exp)
|
|
15
|
+
return false;
|
|
16
|
+
if (Date.now() > exp) {
|
|
17
|
+
this.dynamic.delete(ip);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
block(ip, ttl) {
|
|
23
|
+
this.dynamic.set(ip, Date.now() + (ttl ?? this.ttl));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.MemoryBlacklistStore = MemoryBlacklistStore;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BlacklistStore } from './blacklist.interface';
|
|
2
|
+
interface RedisLike {
|
|
3
|
+
get(key: string): Promise<string | null>;
|
|
4
|
+
set(key: string, value: string, ...args: any[]): Promise<any>;
|
|
5
|
+
del(key: string): Promise<any>;
|
|
6
|
+
}
|
|
7
|
+
export declare class RedisBlacklistStore implements BlacklistStore {
|
|
8
|
+
private redis;
|
|
9
|
+
private staticBlacklist;
|
|
10
|
+
private ttlSeconds;
|
|
11
|
+
private prefix;
|
|
12
|
+
constructor(redis: RedisLike, staticBlacklist?: string[], ttlSeconds?: number);
|
|
13
|
+
private key;
|
|
14
|
+
isBlocked(ip: string): Promise<boolean>;
|
|
15
|
+
block(ip: string, ttlSeconds?: number): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisBlacklistStore = void 0;
|
|
4
|
+
class RedisBlacklistStore {
|
|
5
|
+
constructor(redis, staticBlacklist = [], ttlSeconds = 600) {
|
|
6
|
+
this.redis = redis;
|
|
7
|
+
this.staticBlacklist = staticBlacklist;
|
|
8
|
+
this.ttlSeconds = ttlSeconds;
|
|
9
|
+
this.prefix = 'security:blacklist:';
|
|
10
|
+
}
|
|
11
|
+
key(ip) {
|
|
12
|
+
return `${this.prefix}${ip}`;
|
|
13
|
+
}
|
|
14
|
+
async isBlocked(ip) {
|
|
15
|
+
if (this.staticBlacklist.includes(ip))
|
|
16
|
+
return true;
|
|
17
|
+
const value = await this.redis.get(this.key(ip));
|
|
18
|
+
return value === '1';
|
|
19
|
+
}
|
|
20
|
+
async block(ip, ttlSeconds) {
|
|
21
|
+
const ttl = ttlSeconds ?? this.ttlSeconds;
|
|
22
|
+
await this.redis.set(this.key(ip), '1', 'EX', ttl);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.RedisBlacklistStore = RedisBlacklistStore;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BotDetector = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
class BotDetector {
|
|
6
|
+
constructor(limit = 8) {
|
|
7
|
+
this.limit = limit;
|
|
8
|
+
this.scores = new Map();
|
|
9
|
+
}
|
|
10
|
+
detect(req) {
|
|
11
|
+
let score = 0;
|
|
12
|
+
const ua = (req.headers?.['user-agent'] || '').toLowerCase();
|
|
13
|
+
if (!ua || ua.length < 20)
|
|
14
|
+
score += 2;
|
|
15
|
+
if (/(curl|wget|python|java|scrapy|axios|httpclient|go-http)/.test(ua))
|
|
16
|
+
score += 5;
|
|
17
|
+
if (!req.headers?.accept)
|
|
18
|
+
score += 1;
|
|
19
|
+
if (!req.headers?.['accept-language'])
|
|
20
|
+
score += 1;
|
|
21
|
+
if (!req.headers?.['sec-fetch-site'])
|
|
22
|
+
score += 2;
|
|
23
|
+
if (!req.headers?.cookie)
|
|
24
|
+
score += 1;
|
|
25
|
+
if (/(wp-admin|\.env|phpmyadmin|cgi-bin)/i.test(req.originalUrl))
|
|
26
|
+
score += 5;
|
|
27
|
+
const ip = (0, utils_1.getClientIP)(req);
|
|
28
|
+
const total = (this.scores.get(ip) || 0) + score;
|
|
29
|
+
this.scores.set(ip, total);
|
|
30
|
+
return total >= this.limit;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.BotDetector = BotDetector;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./apply-security"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./utils"), exports);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createRateLimiter = createRateLimiter;
|
|
7
|
+
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
|
8
|
+
function createRateLimiter(options, onLimit) {
|
|
9
|
+
return (0, express_rate_limit_1.default)({
|
|
10
|
+
windowMs: options.windowMs,
|
|
11
|
+
max: options.max,
|
|
12
|
+
standardHeaders: true,
|
|
13
|
+
legacyHeaders: false,
|
|
14
|
+
handler: (req, res) => {
|
|
15
|
+
onLimit(req, res);
|
|
16
|
+
res.status(429).send('Too many requests');
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BlacklistStore } from './blacklist/blacklist.interface';
|
|
2
|
+
export type BlockReason = 'rate-limit' | 'bot-detected' | 'path-not-allowed' | 'error-spam' | 'blacklist';
|
|
3
|
+
export interface BlockInfo {
|
|
4
|
+
ip: string;
|
|
5
|
+
reason: BlockReason;
|
|
6
|
+
url: string;
|
|
7
|
+
ua?: string;
|
|
8
|
+
extra?: any;
|
|
9
|
+
}
|
|
10
|
+
export interface RateLimitOptions {
|
|
11
|
+
windowMs: number;
|
|
12
|
+
max: number;
|
|
13
|
+
}
|
|
14
|
+
export interface BotOptions {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
scoreLimit?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SecurityOptions {
|
|
19
|
+
/** dev β skip security */
|
|
20
|
+
mode?: 'prod' | 'dev';
|
|
21
|
+
/** allow only these paths */
|
|
22
|
+
pathWhitelist: string[];
|
|
23
|
+
/** hard blacklist */
|
|
24
|
+
staticBlacklist?: string[];
|
|
25
|
+
/** dynamic blacklist ttl (ms) */
|
|
26
|
+
blacklistTTL?: number;
|
|
27
|
+
/** custom blacklist store (Redis / Memory) */
|
|
28
|
+
blacklist?: {
|
|
29
|
+
store?: BlacklistStore;
|
|
30
|
+
};
|
|
31
|
+
/** rate limit */
|
|
32
|
+
rateLimit?: RateLimitOptions;
|
|
33
|
+
/** bot detection */
|
|
34
|
+
bot?: BotOptions;
|
|
35
|
+
/** notify app when blocked */
|
|
36
|
+
onBlock?: (info: BlockInfo) => void;
|
|
37
|
+
}
|
package/dist/types.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getClientIP(req: any): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getClientIP = getClientIP;
|
|
4
|
+
function getClientIP(req) {
|
|
5
|
+
return (req.headers?.['x-real-ip'] ||
|
|
6
|
+
req.headers?.['x-forwarded-for']?.split(',')[0] ||
|
|
7
|
+
req.socket?.remoteAddress ||
|
|
8
|
+
req.ip ||
|
|
9
|
+
'unknown');
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auto-smart-security",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready security middleware for Express / NestJS",
|
|
5
|
+
"author": "Hai Vinh <haivinhinspirit@gmail.com>",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"readmeFilename": "README.md",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"start:dev": "tsc -w"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"security",
|
|
21
|
+
"express",
|
|
22
|
+
"nestjs",
|
|
23
|
+
"rate-limit",
|
|
24
|
+
"bot-detection",
|
|
25
|
+
"blacklist"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"express": ">=4",
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"express-rate-limit": "^7.0.0",
|
|
36
|
+
"helmet": "^7.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/express": "^5.0.6",
|
|
40
|
+
"@types/node": "^25.0.3",
|
|
41
|
+
"typescript": "^5.3.0"
|
|
42
|
+
}
|
|
43
|
+
}
|