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 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,2 @@
1
+ import { SecurityOptions } from './types';
2
+ export declare function applySecurity(app: any, options: SecurityOptions): void;
@@ -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,4 @@
1
+ export interface BlacklistStore {
2
+ isBlocked(ip: string): Promise<boolean> | boolean;
3
+ block(ip: string, ttl?: number): Promise<void> | void;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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,6 @@
1
+ export declare class BotDetector {
2
+ private limit;
3
+ private scores;
4
+ constructor(limit?: number);
5
+ detect(req: any): boolean;
6
+ }
@@ -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;
@@ -0,0 +1,3 @@
1
+ export * from './apply-security';
2
+ export * from './types';
3
+ export * from './utils';
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,4 @@
1
+ export declare function createRateLimiter(options: {
2
+ windowMs: number;
3
+ max: number;
4
+ }, onLimit: (req: any, res: any) => void): import("express-rate-limit").RateLimitRequestHandler;
@@ -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
+ }
@@ -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
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }