auto-smart-security
v2.0.0
Published
Production-ready security middleware for Express / NestJS
Maintainers
Readme
auto-smart-security
Production-ready security middleware for Express and NestJS — one line to protect your API from bots, abuse, and attacks.
applySecurity(app, { preset: 'public-api', trustProxy: 1 });Why this exists
You can wire up helmet + express-rate-limit + cors + a CSRF lib + sanitizers yourself — and keep them all configured correctly across every project. This library bundles those layers behind one config object, adds an adaptive trust engine (callers you trust get higher limits and skip blocking) and auto-blacklisting (repeat offenders are blocked automatically), and ships use-case presets so you start from a safe default instead of a blank page.
It is one layer of defense at the request edge — not a replacement for auth, ORM-level injection safety, or secrets management. See What This Library Does NOT Do.
How it works — the layers
Every request flows through these layers in order. Each option below maps to exactly one layer:
Request
│
├─ Edge HTTPS enforce · CORS · body size limit
├─ Identity trust engine (IP / origin / path → trust level)
├─ Abuse rate limit · bot detection · blacklist
├─ Payload sanitize (NoSQL/XSS) · CSRF
│
▼
Your appTrusted callers (trust level ≥ 5) get higher rate limits and are never blocked — only observed via onBlock.
Features
- Security Presets —
minimal,standard, orstrictwith one line of config - CORS — built-in configurable CORS support
- HTTPS Enforcement — redirect or block non-HTTPS requests
- Body Size Limit — reject oversized payloads before parsing
- Helmet — secure HTTP headers with configurable options
- CSRF Protection — stateless double-submit cookie pattern
- Input Sanitization — blocks NoSQL/prototype-pollution, optional XSS stripping
- Adaptive Rate Limiting — different limits per trust level
- Bot Detection — score-based with configurable patterns
- IP Blacklist — auto-blacklist with TTL (Memory or Redis)
- Path Whitelist — block API scanning and unknown routes
- Trust Engine — whitelist IPs, origins, and paths
- Bypass Paths — skip checks for webhooks and OAuth callbacks
- Builder Pattern — fluent API with full TypeScript autocomplete
- Graceful Shutdown — clean up timers on app exit
Installation
npm install auto-smart-securityPeer dependency:
npm install expressQuick Start
Using Presets (Recommended)
import express from 'express';
import { applySecurity } from 'auto-smart-security';
const app = express();
const security = applySecurity(app, {
preset: 'public-api', // or 'web-app' / 'internal'
trustProxy: 1,
});
app.get('/api/health', (req, res) => res.json({ ok: true }));
app.listen(3000);
// On shutdown
process.on('SIGTERM', () => security.shutdown());Using Builder Pattern
import express from 'express';
import { createSecurity } from 'auto-smart-security';
const app = express();
const security = createSecurity()
.preset('standard')
.trustProxy(1)
.cors({ origin: ['https://my-app.com'], credentials: true })
.pathWhitelist(['api/v1', 'health'])
.onBlock(({ ip, reason }) => console.warn(`Blocked ${ip}: ${reason}`))
.apply(app);
app.listen(3000);Presets
Pick the preset that matches what you're building — these are the recommended starting points:
| Preset | Use it for | CORS | CSRF | HTTPS | Sanitize |
|--------|-----------|------|------|-------|----------|
| public-api | REST/JSON API with token auth (JWT/Bearer) | * | off | off | NoSQL |
| web-app | Browser app with cookie sessions | credentialed | on | off | NoSQL + XSS |
| internal | Admin / internal service | locked | off | block | NoSQL + XSS |
// Building a public REST API?
applySecurity(app, { preset: 'public-api', trustProxy: 1 });
// Building a cookie-session web app?
applySecurity(app, { preset: 'web-app', trustProxy: 1, cors: { origin: ['https://my-app.com'], credentials: true } });Why is CSRF only on for
web-app? CSRF attacks rely on the browser auto-sending cookies. Token-based APIs (JWT in theAuthorizationheader) are immune, so enabling CSRF there would just break clients for no benefit.
Strength-based presets
If you prefer to think in terms of "how much", these also exist:
| Feature | minimal | standard | strict |
|---------|-----------|------------|----------|
| Helmet | Yes | Yes | Yes (strict CSP) |
| CORS | origin: * | origin: * | origin: [] (must specify) |
| Rate Limit | - | 100/min | 60/min (trusted: 300/min) |
| Bot Detection | - | Yes (score 8) | Yes (score 6) |
| Input Sanitization | - | NoSQL | NoSQL + XSS |
| HTTPS Enforcement | - | - | Redirect |
| Body Size Limit | - | - | 1mb |
| Blacklist TTL | 10min | 10min | 30min |
| CSRF | - | - | - (opt-in) |
Any preset is just a set of defaults — every option you pass overrides it.
Startup summary
On boot, the library prints what's active and warns about loose config (pass silent: true to disable):
🔒 auto-smart-security (preset: public-api)
✓ CORS ✓ Rate limit (100/60s) ✓ Bot detection
✓ Sanitize ✗ HTTPS ✗ CSRF
⚠ CORS origin is '*' — tighten to explicit origins for production.
⚠ HTTPS enforcement is off — ensure TLS terminates at your proxy.Presets provide sensible defaults. Override any option:
applySecurity(app, {
preset: 'strict',
trustProxy: 1,
cors: { origin: ['https://my-app.com'], credentials: true },
rateLimit: { default: { max: 200, windowMs: 60_000 } }, // override strict's 60/min
});Configuration Reference
interface SecurityOptions {
/** 'dev' skips all security checks */
mode?: 'prod' | 'dev';
/** Security preset: 'minimal' | 'standard' | 'strict' */
preset?: PresetName;
/** Number of trusted proxy hops (required when rateLimit is set) */
trustProxy?: number;
/** CORS: true for permissive, or CorsOptions */
cors?: boolean | CorsOptions;
/** HTTPS enforcement: true for redirect, or HttpsOptions */
https?: boolean | HttpsOptions;
/** CSRF protection (double-submit cookie): true or CsrfOptions */
csrf?: boolean | CsrfOptions;
/** Input sanitization (NoSQL/XSS): true or SanitizeOptions */
sanitize?: boolean | SanitizeOptions;
/** Body size limit: true for 1mb, string like '2mb', or BodyLimitOptions */
bodyLimit?: boolean | string | BodyLimitOptions;
/** Helmet options — merged with secure defaults */
helmet?: Record<string, any>;
/** Adaptive rate limit */
rateLimit?: AdaptiveRateLimit;
/** Bot detection */
bot?: BotOptions;
/** Trust level config */
trust?: TrustOptions;
/** Only these path prefixes are allowed. Omit to allow all. */
pathWhitelist?: string[];
/** Always-blocked IPs */
staticBlacklist?: string[];
/** Dynamic blacklist duration in ms (default: 600000) */
blacklistTTL?: number;
/** Custom blacklist store (Memory or Redis) */
blacklist?: { store?: BlacklistStore };
/** Required headers (missing → bot score +2 per header) */
requiredHeader?: string[];
/** Paths that skip ALL security checks */
bypassPaths?: string[];
/** Called whenever a request is blocked */
onBlock?: (info: BlockInfo) => void;
}CORS
applySecurity(app, {
cors: {
origin: ['https://my-app.com', 'https://admin.my-app.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
},
// ...
});| Option | Type | Default |
|--------|------|---------|
| origin | string \| string[] \| (origin) => boolean | '*' |
| methods | string[] | ['GET','HEAD','PUT','PATCH','POST','DELETE'] |
| allowedHeaders | string[] | Reflects request headers |
| exposedHeaders | string[] | — |
| credentials | boolean | false |
| maxAge | number (seconds) | 86400 |
Note:
credentials: truecannot be used withorigin: '*'. Specify explicit origins.
HTTPS Enforcement
applySecurity(app, {
https: true, // redirect HTTP → HTTPS
// or:
https: {
mode: 'block', // return 403 instead of redirect
excludePaths: ['/health'], // skip for health checks
},
});Body Size Limit
applySecurity(app, {
bodyLimit: '2mb',
// or:
bodyLimit: { maxSize: '500kb' },
// or:
bodyLimit: true, // default 1mb
});Rejects requests with Content-Length exceeding the limit (413 Payload Too Large). Also monitors chunked transfers.
CSRF Protection
Stateless double-submit cookie pattern — no server session required. A token cookie is issued on safe requests; state-changing requests (POST/PUT/PATCH/DELETE) must echo it in a header.
applySecurity(app, {
csrf: true,
// or:
csrf: {
cookieName: 'csrf-token', // default
headerName: 'x-csrf-token', // default
protectedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
excludePaths: ['/webhooks'],
secure: true, // Secure cookie flag
sameSite: 'lax', // 'strict' | 'lax' | 'none'
},
});The client reads the csrf-token cookie and sends it back in the x-csrf-token header. Invalid/missing token → 403 Invalid CSRF token.
When to use: Only for cookie-based session auth. APIs using
Authorization: Bearer <token>(JWT) do not need CSRF and should leave this off. That's why no preset enables it.
Input Sanitization
Recursively cleans req.body, req.query, and req.params:
applySecurity(app, {
sanitize: true,
// or:
sanitize: {
mongo: true, // strip $, ., __proto__ keys (NoSQL injection / prototype pollution)
xss: false, // strip < > from string values
targets: ['body', 'query', 'params'],
},
});mongo(defaulttrue): removes keys starting with$, containing., or named__proto__/constructor/prototype— blocks NoSQL operator injection and prototype pollution.xss(defaultfalse): strips<and>from string values. Use with care; for rich content prefer output-encoding at render time.
This is a defense-in-depth layer, not a replacement for parameterized queries / ORM and proper output encoding.
Helmet
Secure HTTP headers are applied by default with safe settings. Customize:
applySecurity(app, {
helmet: {
contentSecurityPolicy: false, // disable CSP
crossOriginResourcePolicy: false, // allow cross-origin resources
},
});Adaptive Rate Limiting
applySecurity(app, {
trustProxy: 1,
rateLimit: {
default: { max: 100, windowMs: 60_000 }, // untrusted callers
trusted: { max: 500, windowMs: 60_000 }, // trust level >= 4
internal: { max: 2000, windowMs: 60_000 }, // trust level >= 7
},
});Returns 429 Too many requests when exceeded. The IP is auto-blacklisted.
trustProxyis required whenrateLimitis set. Set to the number of proxy hops (usually1).
Bot Detection
applySecurity(app, {
bot: {
enabled: true,
scoreLimit: 8,
excludePatterns: ['python'], // remove 'python' from default bot list
additionalPatterns: [/my-bot/i], // add custom patterns
scanPaths: [/\.git/i], // additional scan path patterns
},
requiredHeader: ['x-app-token'],
});Default scoring:
| Signal | Score |
|--------|-------|
| Missing or short User-Agent (< 20 chars) | +2 |
| Known bot UA (curl, wget, python, scrapy, go-http, ...) | +10 |
| Scanning paths (wp-admin, .env, phpmyadmin, cgi-bin) | +100 |
| Missing required header (per header) | +2 |
Note:
axioswas removed from the default bot list in v2.0 (it's commonly used in legitimate server-to-server calls).
Path Whitelist
applySecurity(app, {
pathWhitelist: ['api/v1', 'health', 'media'],
});"api/v1"matches/api/v1and/api/v1/anything- Does not match
/other-api/v1 - Static assets (
.png,.css,.js,.woff, etc.) always skip checks - After 10 violations in 1 minute, the IP is blacklisted
- Optional in v2.0 — omit to allow all paths
Trust Engine
applySecurity(app, {
trust: {
ips: ['10.0.0.1', '10.0.0.2'], // +5 — fully trusted
// or: ips: (ip) => ip.startsWith('10.'),
origins: ['https://my-frontend.com'], // +3 — trusted origin
paths: ['internal/'], // +2 — trusted path prefix
},
});| Source | Level |
|--------|-------|
| IP in trust.ips | +5 |
| Origin/Referer matches trust.origins | +3 |
| Path matches trust.paths | +2 |
Level >= 5: request is never blocked (onBlock still fires for observability).
IP Blacklist
Static — always blocked:
applySecurity(app, {
staticBlacklist: ['1.2.3.4', '5.6.7.8'],
});Dynamic — auto-blacklisted after violations:
applySecurity(app, {
blacklistTTL: 10 * 60 * 1000, // 10 minutes (default)
});Redis — share across instances:
import { RedisBlacklistStore } from 'auto-smart-security';
import Redis from 'ioredis';
const redis = new Redis();
applySecurity(app, {
trustProxy: 1,
blacklist: {
store: new RedisBlacklistStore(redis, ['1.2.3.4'], 600),
},
});Bypass Paths
applySecurity(app, {
bypassPaths: [
'/webhooks/instagram',
'/webhooks/stripe',
'/auth/callback',
],
});Bypassed paths skip ALL security checks. Use only for third-party endpoints.
onBlock Hook
applySecurity(app, {
onBlock: ({ ip, reason, url, ua }) => {
console.warn(`Blocked ${ip} — ${reason} — ${url}`);
// Send to Slack, Telegram, Sentry, etc.
},
});Block reasons: rate-limit, bot-detected, path-not-allowed, blacklist, https-required, payload-too-large, csrf
NestJS Integration
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();
const security = applySecurity(httpAdapter, {
preset: 'standard',
mode: process.env.NODE_ENV === 'production' ? 'prod' : 'dev',
trustProxy: 1,
cors: { origin: ['https://my-app.com'], credentials: true },
onBlock: ({ ip, reason }) => console.warn(`Blocked ${ip}: ${reason}`),
});
await app.listen(3000);
process.on('SIGTERM', () => {
security.shutdown();
app.close();
});
}
bootstrap();Custom Blacklist Store
import { BlacklistStore } from 'auto-smart-security';
class MyStore implements BlacklistStore {
async isBlocked(ip: string): Promise<boolean> { /* ... */ }
async block(ip: string, ttlMs?: number): Promise<void> { /* ... */ }
}Graceful Shutdown
applySecurity returns a SecurityInstance with a shutdown() method that clears all internal timers:
const security = applySecurity(app, { preset: 'standard' });
process.on('SIGTERM', () => security.shutdown());Migration from v1.x
Breaking Changes
getClientIPnow usesreq.ip— no longer reads rawX-Real-IP/X-Forwarded-Forheaders directly. Express'strust proxysetting handles this correctly. If you relied on raw header extraction, settrust proxyproperly instead.Origin trust uses exact hostname matching —
startsWithreplaced with URL parsing.https://example.com.evil.comno longer matches trustedhttps://example.com.applySecurityreturnsSecurityInstance— callsecurity.shutdown()on app exit. Existing code that ignores the return value still works.Helmet defaults changed —
crossOriginResourcePolicyandcrossOriginOpenerPolicynow default to'same-origin'instead offalse. If you serve cross-origin resources, addhelmet: { crossOriginResourcePolicy: false }.pathWhitelistis now optional — omit it to allow all paths (previously required).axiosremoved from default bot detection — add it back withbot: { additionalPatterns: [/axios/i] }if needed.
New Features
preset: 'minimal' | 'standard' | 'strict'cors: true | CorsOptionshttps: true | HttpsOptionsbodyLimit: true | string | BodyLimitOptionshelmet: Record<string, any>createSecurity()builder pattern- Configurable bot detection patterns
- Options validation with clear error messages
What This Library Does NOT Do
This is a request/network-layer security middleware. It is one layer of defense, not a complete security solution. The following are your application's responsibility:
| Concern | Use instead |
|---------|-------------|
| Authentication (login, JWT/OAuth) | Passport, Auth0, your own auth |
| Password hashing | bcrypt, Argon2 |
| Authorization / RBAC | Your app logic, CASL |
| SQL injection prevention | Parameterized queries, your ORM |
| Secrets management | AWS Secrets Manager, Vault, .env |
| Database / infra security | Your DB & cloud config |
| Output encoding (full XSS defense) | Your template/render layer |
Use this library to harden the edge; combine it with the above for full coverage.
License
MIT — Hai Vinh
