auto-smart-security
v1.2.0
Published
Production-ready security middleware for Express / NestJS
Maintainers
Readme
auto-smart-security
Production-ready security middleware for Express and NestJS.
Protect your API from bots, abuse, and attacks with adaptive rate limiting, auto-blacklist, bot detection, and Redis support.
Features
- Path whitelist — block API scanning and unknown routes
- Adaptive rate limiting — different limits per trust level
- Automatic IP blacklist with TTL (Memory or Redis)
- Score-based bot detection
- Required header enforcement
- Trust engine — whitelist IPs, origins, and paths
onBlockhook for logging and notifications- Multi-instance / Docker / Kubernetes safe (via Redis store)
- Works with Express and NestJS
Installation
npm install auto-smart-securityPeer dependency — your app must have express installed:
npm install expressQuick Start
import express from 'express';
import { applySecurity } from 'auto-smart-security';
const app = express();
applySecurity(app, {
mode: 'prod',
trustProxy: 1,
pathWhitelist: ['api/v1'],
rateLimit: {
default: { max: 100, windowMs: 60_000 },
},
bot: { enabled: true },
onBlock: ({ ip, reason, url }) => {
console.warn(`Blocked ${ip} — ${reason} — ${url}`);
},
});
app.listen(3000);Options
interface SecurityOptions {
/** 'dev' skips all security checks */
mode?: 'prod' | 'dev';
/** Number of trusted proxy hops (required when rateLimit is set) */
trustProxy?: number;
/** Only these path prefixes are allowed */
pathWhitelist: string[];
/** IPs that are always blocked */
staticBlacklist?: string[];
/** Dynamic blacklist duration in milliseconds (default: 10 min) */
blacklistTTL?: number;
/** Custom blacklist store — Memory (default) or Redis */
blacklist?: { store?: BlacklistStore };
/** Adaptive rate limit config */
rateLimit?: AdaptiveRateLimit;
/** Bot detection config */
bot?: BotOptions;
/** Trust level config */
trust?: TrustOptions;
/** Headers that every request must include */
requiredHeader?: string[];
/** Called whenever a request is blocked */
onBlock?: (info: BlockInfo) => void;
}Path Whitelist
Only requests to whitelisted path prefixes are allowed. Requests to any other path are tracked; after 10 violations within 1 minute the IP is blacklisted.
applySecurity(app, {
pathWhitelist: [
'api/v1', // allows /api/v1, /api/v1/users, /api/v1/products/123
'health', // allows /health exactly
'media', // allows /media/...
],
});Rules:
- Entry
"api/v1"matches/api/v1(exact) and/api/v1/anything(prefix +/) - It does not match
/other-api/v1or/api/v1inject - Static assets (
.png,.jpg,.css,.js,.woff, etc.) are always allowed and skip all checks
Bot Detection
Scores each request using heuristics. When the score exceeds the limit the IP is blocked and added to the dynamic blacklist.
applySecurity(app, {
bot: {
enabled: true,
scoreLimit: 8, // default: 8
},
pathWhitelist: ['api'],
});Scoring rules:
| Signal | Score |
|--------|-------|
| Missing or short User-Agent (< 20 chars) | +2 |
| Known bot/tool UA (curl, wget, python, scrapy, axios, go-http, ...) | +10 |
| Scanning paths (wp-admin, .env, phpmyadmin, cgi-bin) | +100 |
| Missing required header (per header) | +2 |
Scores accumulate within a fixed 1-minute window per IP. After the window expires the score resets.
Required Headers
Enforce that every request includes specific headers. Missing headers add to the bot score.
applySecurity(app, {
requiredHeader: ['x-app-token', 'x-client-version'],
bot: { enabled: true, scoreLimit: 4 },
pathWhitelist: ['api'],
});This is effective against generic scanners that don't know your custom headers.
Adaptive Rate Limiting
Rate limit thresholds adapt based on the caller's trust level.
applySecurity(app, {
trustProxy: 1,
rateLimit: {
default: { max: 100, windowMs: 60_000 }, // all untrusted callers
trusted: { max: 500, windowMs: 60_000 }, // trust level >= 4
internal: { max: 2000, windowMs: 60_000 }, // trust level >= 7
},
pathWhitelist: ['api'],
});Returns 429 Too many requests when exceeded. The IP is automatically added to the dynamic blacklist.
trustProxyis required whenrateLimitis set. Set it to the number of proxy hops in front of your app (usually1behind nginx or Cloudflare). Never usetrue.
Trust Engine
Assign trust levels to callers so they get higher rate limits or bypass blocking entirely.
applySecurity(app, {
trust: {
ips: ['10.0.0.1', '10.0.0.2'], // +5 — fully trusted, never blocked
// or a function:
// ips: (ip) => ip.startsWith('10.'),
origins: ['https://my-frontend.com'], // +3 — trusted origin/referer header
paths: ['internal/'], // +2 — trusted path prefix
},
pathWhitelist: ['api'],
trustProxy: 1,
});Trust level table:
| Source | Level gained |
|--------|-------------|
| IP in trust.ips | +5 |
| Origin/Referer matches trust.origins | +3 |
| Path matches trust.paths | +2 |
Level >= 5: the onBlock callback is still fired (for observability) but the request is not blocked.
IP Blacklist
Static blacklist — always blocked, loaded at startup:
applySecurity(app, {
staticBlacklist: ['1.2.3.4', '5.6.7.8'],
pathWhitelist: ['api'],
});Dynamic blacklist — IPs blocked at runtime after bot detection, rate limit violation, or repeated path violations:
applySecurity(app, {
blacklistTTL: 10 * 60 * 1000, // 10 minutes (default)
pathWhitelist: ['api'],
});Redis store — share the blacklist across multiple instances:
import { RedisBlacklistStore } from 'auto-smart-security';
import Redis from 'ioredis';
const redis = new Redis({
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT ?? 6379),
password: process.env.REDIS_PASSWORD,
db: Number(process.env.REDIS_DB ?? 0),
});
applySecurity(app, {
trustProxy: 1,
blacklist: {
store: new RedisBlacklistStore(
redis,
['1.2.3.4'], // static blacklist
600, // TTL in seconds
),
},
pathWhitelist: ['api'],
});onBlock Hook
Receive an event every time a request is blocked:
applySecurity(app, {
pathWhitelist: ['api'],
onBlock: ({ ip, reason, url, ua }) => {
console.warn(`Blocked ${ip} — ${reason} — ${url}`);
// Integrate with Slack, Telegram, Sentry, etc.
},
});Block reasons:
| Reason | Description |
|--------|-------------|
| rate-limit | Too many requests |
| bot-detected | Bot behavior detected |
| path-not-allowed | Path not in whitelist |
| blacklist | IP is blacklisted |
NestJS Integration
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { applySecurity } from 'auto-smart-security';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const httpAdapter = app.getHttpAdapter().getInstance();
applySecurity(httpAdapter, {
mode: process.env.NODE_ENV === 'production' ? 'prod' : 'dev',
trustProxy: 1,
pathWhitelist: ['api'],
rateLimit: {
default: { max: 100, windowMs: 60_000 },
trusted: { max: 500, windowMs: 60_000 },
},
bot: { enabled: true },
staticBlacklist: [],
onBlock: ({ ip, reason }) => console.warn(`Blocked ${ip}: ${reason}`),
});
await app.listen(3000);
}
bootstrap();Custom Blacklist Store
Implement BlacklistStore to use any storage backend:
import { BlacklistStore } from 'auto-smart-security';
class MyStore implements BlacklistStore {
async isBlocked(ip: string): Promise<boolean> {
// return true if ip should be blocked
}
async block(ip: string, ttlMs?: number): Promise<void> {
// ttlMs is always in milliseconds
}
}Development Mode
Skip all security checks during local development:
applySecurity(app, {
mode: process.env.NODE_ENV === 'production' ? 'prod' : 'dev',
pathWhitelist: ['api'],
});Best Practices
- Set
trustProxyto the number of real proxy hops (usually1). Never usetrue. - Whitelist only valid API prefixes — keep the list tight.
- Use
RedisBlacklistStorein multi-instance or Kubernetes environments. - Use
requiredHeaderwith a shared secret header your frontend always sends. - Wire up
onBlockto a notification channel (Slack, Telegram, Sentry) for real-time visibility. - Use
staticBlacklistfor known bad actors that should never be let in.
Changelog
1.2.0
- Fix:
blacklistTTLunit mismatch — RedisStore now correctly converts ms to seconds - Fix: rate limiter
onLimitwas receivingreqobject instead of the IP string - Fix:
Authorizationheader presence no longer grants trust level without validation - Fix: path whitelist uses
startsWithinstead ofincludesto prevent prefix bypass attacks - Fix: bot score window now stays fixed per IP instead of resetting on every request
- Fix: removed duplicate static asset check
- Fix:
pathViolationMapand bot score map are now cleaned up periodically (memory leak) - Fix: removed all debug
console.logstatements
1.1.3
- Add required header enforcement
1.1.0
- Add trust engine and adaptive rate limiting
1.0.0
- Initial release
License
MIT — Hai Vinh
