@chdlabs/my-shield 0.2.0 → 0.3.1
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 +212 -226
- package/package.json +5 -1
- package/src/admin/otpGate.js +370 -0
- package/src/client/collector.js +303 -53
- package/src/client/index.js +2 -1
- package/src/client/shieldClient.js +121 -0
- package/src/core/ipResolver.js +225 -0
- package/src/index.d.ts +152 -10
- package/src/index.js +113 -4
- package/src/middleware/behavioralGuard.js +172 -24
- package/src/middleware/honeypot.js +6 -4
- package/src/middleware/notFoundCollector.js +6 -4
- package/src/middleware/shieldGuard.js +3 -0
- package/src/storage/BaseAdapter.js +58 -0
- package/src/storage/CallbackStorageAdapter.js +69 -0
- package/src/storage/FileStorageAdapter.js +174 -0
- package/src/storage/MemoryAdapter.js +56 -0
- package/src/storage/index.js +53 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🛡️ @chdlabs/my-shield
|
|
2
2
|
|
|
3
|
-
> High-performance, zero-latency security shield middleware for Express. Built with pure JavaScript probabilistic data structures (Bloom filter + LRU cache), honeypot traps, and
|
|
3
|
+
> High-performance, zero-latency security shield middleware for Express. Built with pure JavaScript probabilistic data structures (Bloom filter + LRU cache), honeypot traps, MiniRocket behavioral biometrics, pluggable threat persistence, and container-native enterprise IP resolution.
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://nodejs.org/)
|
|
@@ -12,13 +12,16 @@
|
|
|
12
12
|
|
|
13
13
|
- **Sub-0.1ms Clean Pass**: Legitimate requests pass through the L1 Bloom filter in `< 0.1ms`.
|
|
14
14
|
- **Zero False-Positive Blocks**: Two-tiered L1 caching (Bloom filter + LRU confirmed threat cache) guarantees statistical Bloom false positives are never blocked.
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
15
|
+
- **Enterprise Client IP Resolution (`IPResolver`)**: Production-tested parsing for Cloudflare (`CF-Connecting-IP`), Akamai (`True-Client-IP`), Nginx (`X-Real-IP`), and multi-hop HAProxy chains (`X-Forwarded-For`) with IPv6-mapped IPv4 normalization and port stripping.
|
|
16
|
+
- **Docker & Gateway Protection**: Docker bridge gateways (`172.17.0.1`) and loopback addresses (`127.0.0.1`, `::1`) are permanently immune from bans, preventing accidental container isolation.
|
|
17
|
+
- **Mission-Critical Route Protection**: First-class `shield.strictBotGuard()` to protect `/login`, `/checkout`, and `/register` against credential stuffing, carding (card testing), and automated account spam.
|
|
18
|
+
- **Dual-Mode Behavioral Biometrics**: Calibrated Ridge decision boundary (`0.50`) supporting non-blocking `'audit'` mode (for searches and queries) alongside strict `'block'` mode (for auth and checkout).
|
|
19
|
+
- **First-Party Client SDK (`@chdlabs/my-shield/client`)**: Unified `ShieldClient` and `PointerTelemetryCollector` with mobile touch support (`pointerdown`, `touchmove`, tap dwell kinetics), pointer freshness guards ($t_{\text{idle}} \le 2.5\text{s}$), buffer flushes on consumption, and automatic `fetch`/`Axios` interceptors.
|
|
20
|
+
- **Pluggable Threat Persistence**: Zero-overhead in-memory adapter, local JSON file persistence (`FileStorageAdapter`), or universal database hooks (`PostgreSQL`, `MySQL`, `Redis`, `Prisma`).
|
|
21
|
+
- **Built-in Admin OTP / MFA Challenge Gate**: Standalone, dark-mode OTP verification portal (`shield.otpGate()`) with timing-safe HMAC validation and brute-force protection.
|
|
22
|
+
- **Silent Honeypot Decoys**: Static trap routes (`/.env`, `/wp-login.php`, `/.git/config`, etc.) return a silent `200 OK` decoy response and instantly blacklist scanners.
|
|
17
23
|
- **404 Anomaly & Intelligence**: Captures every 404 URL probe, bans scanners probing $\ge 5$ unique paths in 60s, and auto-promotes frequent attack targets to active honeypots.
|
|
18
|
-
- **
|
|
19
|
-
- **Self-Contained Security Dashboard**: Built-in dark-themed telemetry console with real-time Server-Sent Events (SSE), charts, and active block management.
|
|
20
|
-
- **Safe By Default**: Admin dashboard is disabled by default (`admin.enabled: false`) with dedicated RBAC integration to prevent unauthorized telemetry access.
|
|
21
|
-
- **Zero Production Dependencies**: Pure JavaScript built exclusively with Node.js built-ins (`crypto`, `fs`, `path`, `events`).
|
|
24
|
+
- **Zero Production Dependencies**: Pure JavaScript built exclusively with Node.js built-ins (`crypto`, `fs`, `path`, `events`, `net`).
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
@@ -29,9 +32,14 @@ Incoming Request
|
|
|
29
32
|
│
|
|
30
33
|
▼
|
|
31
34
|
┌───────────────────────────────────────┐
|
|
35
|
+
│ Enterprise IPResolver │
|
|
36
|
+
│ Cloudflare / Nginx / HAProxy / Direct │
|
|
37
|
+
│ Gateway Protection (172.17.0.1 Immune)│
|
|
38
|
+
└──────────────────┬────────────────────┘
|
|
39
|
+
▼
|
|
40
|
+
┌───────────────────────────────────────┐
|
|
32
41
|
│ L1 Shield Guard │
|
|
33
|
-
│
|
|
34
|
-
│ 1. IP Whitelist? ──▶ Pass │
|
|
42
|
+
│ 1. IP Whitelisted? ──▶ Pass │
|
|
35
43
|
│ 2. Bloom Filter? ──▶ False = Pass │
|
|
36
44
|
│ 3. LRU Cache? ──▶ Found = Block │
|
|
37
45
|
└──────────────────┬────────────────────┘
|
|
@@ -43,11 +51,10 @@ Incoming Request
|
|
|
43
51
|
└──────────────────┬────────────────────┘
|
|
44
52
|
▼
|
|
45
53
|
┌───────────────────────────────────────┐
|
|
46
|
-
│
|
|
47
|
-
│
|
|
48
|
-
│
|
|
49
|
-
│
|
|
50
|
-
│ Human probability? ──▶ >= 0.70 Pass │
|
|
54
|
+
│ Route-Level Bot Defenses (Optional) │
|
|
55
|
+
│ • strictBotGuard() on /login & /pay │
|
|
56
|
+
│ • behavioralGuard({ mode: 'audit' }) │
|
|
57
|
+
│ • costThrottle() on high-cost routes │
|
|
51
58
|
└──────────────────┬────────────────────┘
|
|
52
59
|
▼
|
|
53
60
|
┌───────────────────────────────────────┐
|
|
@@ -57,7 +64,7 @@ Incoming Request
|
|
|
57
64
|
┌───────────────────────────────────────┐
|
|
58
65
|
│ 404 Intelligence Collector │
|
|
59
66
|
│ ≥ 5 unique 404s/60s? ──▶ Ban IP │
|
|
60
|
-
│
|
|
67
|
+
│ Auto-promotes frequent targets │
|
|
61
68
|
└───────────────────────────────────────┘
|
|
62
69
|
```
|
|
63
70
|
|
|
@@ -81,63 +88,40 @@ const app = express();
|
|
|
81
88
|
|
|
82
89
|
const shield = createShield({
|
|
83
90
|
whitelist: ['127.0.0.1', '::1'],
|
|
91
|
+
trustProxy: true, // Auto-resolve Cloudflare, Nginx, or HAProxy client IPs
|
|
92
|
+
protectGateways: true, // Prevents 172.17.0.1 & loopbacks from ever being banned
|
|
93
|
+
storage: 'file', // Persist bans to ./data/shield/blocked_ips.json across restarts
|
|
84
94
|
admin: {
|
|
85
|
-
enabled: true // Set to true to activate dashboard
|
|
86
|
-
},
|
|
87
|
-
bloom: {
|
|
88
|
-
expectedItems: 100_000,
|
|
89
|
-
falsePositiveRate: 0.01
|
|
90
|
-
},
|
|
91
|
-
lru: {
|
|
92
|
-
maxSize: 10_000,
|
|
93
|
-
ttlMs: 5 * 60 * 1000 // 5 minutes
|
|
94
|
-
},
|
|
95
|
-
honeypot: {
|
|
96
|
-
enabled: true,
|
|
97
|
-
delayMs: 500
|
|
98
|
-
},
|
|
99
|
-
intelligence: {
|
|
100
|
-
anomalyThreshold: 5, // 5 unique 404s in 60s -> BAN
|
|
101
|
-
anomalyWindowMs: 60_000,
|
|
102
|
-
captureAll: true,
|
|
103
|
-
filePersistence: {
|
|
104
|
-
enabled: true,
|
|
105
|
-
directory: './data/shield',
|
|
106
|
-
filename: '404_intelligence.json',
|
|
107
|
-
intervalMs: 60 * 60 * 1000, // 1 hour save interval
|
|
108
|
-
loadOnStartup: true
|
|
109
|
-
},
|
|
110
|
-
autoPromote: {
|
|
111
|
-
enabled: true,
|
|
112
|
-
minHits: 50,
|
|
113
|
-
minUniqueIPs: 10
|
|
114
|
-
}
|
|
95
|
+
enabled: true // Set to true to activate dashboard (Default: false)
|
|
115
96
|
}
|
|
116
97
|
});
|
|
117
98
|
|
|
99
|
+
// Hydrate persisted threats into Bloom and LRU on application startup
|
|
100
|
+
await shield.init();
|
|
101
|
+
|
|
118
102
|
// 1. Mount main defense guard (FIRST middleware after parsing)
|
|
119
103
|
app.use(shield.middleware());
|
|
120
104
|
|
|
121
105
|
// 2. Mount honeypot trap router (BEFORE application routes)
|
|
122
106
|
app.use(shield.honeypot());
|
|
123
107
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
app.get('/api/data', (req, res) => res.json({ status: 'ok' }));
|
|
127
|
-
// -----------------> ADD IN HERE ALL YOUR APPLICATION ROUTES <----------------//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
// 3. Application Routes
|
|
109
|
+
app.get('/api/catalog', (req, res) => res.json({ items: [] }));
|
|
131
110
|
|
|
132
111
|
// 4. Mount 404 intelligence collector (AFTER application routes)
|
|
133
112
|
app.use(shield.notFoundCollector());
|
|
134
113
|
|
|
135
|
-
// 5.
|
|
136
|
-
app.use('/admin/shield', shield.
|
|
137
|
-
|
|
114
|
+
// 5. Standalone OTP Protected Admin Console
|
|
115
|
+
app.use('/admin/shield', shield.otpGate({
|
|
116
|
+
adminEmail: 'security@company.com',
|
|
117
|
+
async sendOtp(email, code) {
|
|
118
|
+
// Send OTP via your email service (Nodemailer, SendGrid, etc.)
|
|
119
|
+
console.log(`[OTP] Sent code ${code} to ${email}`);
|
|
120
|
+
}
|
|
138
121
|
}));
|
|
122
|
+
app.use('/admin/shield', shield.adminRouter());
|
|
139
123
|
|
|
140
|
-
// Graceful shutdown: flushes
|
|
124
|
+
// Graceful shutdown: flushes intelligence and threat persistence to disk
|
|
141
125
|
process.on('SIGTERM', async () => {
|
|
142
126
|
await shield.shutdown();
|
|
143
127
|
process.exit(0);
|
|
@@ -146,208 +130,219 @@ process.on('SIGTERM', async () => {
|
|
|
146
130
|
|
|
147
131
|
---
|
|
148
132
|
|
|
149
|
-
##
|
|
133
|
+
## 🛡️ Protecting Mission-Critical Routes (`/login`, `/checkout`, `/register`) From Bots
|
|
150
134
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
```javascript
|
|
156
|
-
const shield = createShield({
|
|
157
|
-
admin: {
|
|
158
|
-
enabled: true // Required to activate dashboard routes
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 2. Restricting Access to Administrators Only
|
|
164
|
-
Do **not** wrap `shield.adminRouter()` at the top-level route (e.g., `app.use('/admin/shield', requireAdmin, ...)`). Doing so causes browser page visits to fail with an immediate `401 Unauthorized` JSON response because web browsers do not send `Authorization` headers on initial page navigations.
|
|
165
|
-
|
|
166
|
-
Instead, pass your authentication and authorization middleware into `shield.adminRouter({ authMiddleware })`:
|
|
167
|
-
```javascript
|
|
168
|
-
app.use('/admin/shield', shield.adminRouter({
|
|
169
|
-
authMiddleware: [authenticateUser, requireAdminRole]
|
|
170
|
-
}));
|
|
171
|
-
```
|
|
135
|
+
Automated bots attack high-value mutation endpoints in three distinct ways:
|
|
136
|
+
1. **Credential Stuffing & Brute Force (`/login`)**: Trying thousands of leaked email/password combinations.
|
|
137
|
+
2. **Card Testing / Carding (`/checkout`)**: Testing stolen credit card numbers, which triggers costly payment gateway decline fees and chargebacks.
|
|
138
|
+
3. **Account Farming (`/register`)**: Creating fake accounts for spamming or exploiting promotional credits.
|
|
172
139
|
|
|
173
|
-
|
|
174
|
-
1. **Public Static Delivery**: The browser can freely download the dashboard SPA shell (`index.html`, `dashboard.css`, `dashboard.js`).
|
|
175
|
-
2. **Protected API & Telemetry**: All data-bearing endpoints (`/admin/shield/api/stats`, `/admin/shield/api/blocked`, `/admin/shield/api/stream`, etc.) are strictly guarded by your `authMiddleware`.
|
|
176
|
-
3. **Graceful UI Feedback**: If a non-admin or unauthenticated visitor accesses the page, the dashboard renders an informative warning banner rather than a raw, broken JSON error.
|
|
140
|
+
### 1. Backend Route Protection (`shield.strictBotGuard()`)
|
|
177
141
|
|
|
178
|
-
|
|
142
|
+
Mount `shield.strictBotGuard()` directly on your sensitive POST endpoints. It verifies behavioral mouse dynamics, rejects synthetic or automated bot vectors with `403 Forbidden`, and **instantly auto-bans the attacker's IP**:
|
|
179
143
|
|
|
180
144
|
```javascript
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const authHeader = req.headers.authorization || (req.query?.token ? `Bearer ${req.query.token}` : null);
|
|
184
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
185
|
-
return res.status(401).json({ error: 'Authentication required. Missing Authorization header.' });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const token = authHeader.split(' ')[1];
|
|
189
|
-
try {
|
|
190
|
-
const claims = verifyJwt(token); // Verify your JWT / PQC token
|
|
191
|
-
req.user = claims;
|
|
192
|
-
next();
|
|
193
|
-
} catch (err) {
|
|
194
|
-
return res.status(401).json({ error: 'Invalid or expired session token.' });
|
|
195
|
-
}
|
|
196
|
-
}
|
|
145
|
+
import express from 'express';
|
|
146
|
+
import { createShield } from '@chdlabs/my-shield';
|
|
197
147
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Check user role from token claims or database lookup
|
|
201
|
-
if (!req.user || (req.user.role !== 'admin' && req.user.role !== 'super_admin')) {
|
|
202
|
-
return res.status(403).json({ error: 'Access denied: Administrator privileges required.' });
|
|
203
|
-
}
|
|
204
|
-
next();
|
|
205
|
-
}
|
|
148
|
+
const app = express();
|
|
149
|
+
app.use(express.json());
|
|
206
150
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
authMiddleware: [authenticateUser, requireAdminRole]
|
|
210
|
-
}));
|
|
211
|
-
```
|
|
151
|
+
const shield = createShield({ trustProxy: true });
|
|
152
|
+
await shield.init();
|
|
212
153
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
1. **URL Query Parameter**: Navigating to `http://your-server.com/admin/shield?token=<jwt>` extracts the token into `sessionStorage` and automatically scrubs the parameter from the browser's address bar to prevent token leakage.
|
|
216
|
-
2. **Storage Fallback**: Checks `sessionStorage` and `localStorage` under standard keys: `shield_session_token`, `pqc_session_token`, `token`, `access_token`, `auth_token`, or `jwt`.
|
|
154
|
+
// Global ingress guard
|
|
155
|
+
app.use(shield.middleware());
|
|
217
156
|
|
|
218
|
-
|
|
157
|
+
// ── Protect High-Risk Mutations ──
|
|
219
158
|
|
|
220
|
-
|
|
159
|
+
// Protect Login against credential stuffing
|
|
160
|
+
app.post('/api/auth/login', shield.strictBotGuard(), (req, res) => {
|
|
161
|
+
// Only authentic human users reach this handler!
|
|
162
|
+
res.json({ success: true, token: 'session_token' });
|
|
163
|
+
});
|
|
221
164
|
|
|
222
|
-
|
|
165
|
+
// Protect User Registration against fake accounts
|
|
166
|
+
app.post('/api/auth/register', shield.strictBotGuard(), (req, res) => {
|
|
167
|
+
res.json({ success: true, user: req.body.email });
|
|
168
|
+
});
|
|
223
169
|
|
|
224
|
-
|
|
225
|
-
|
|
170
|
+
// Protect Checkout: Combine Strict Bot Defense with Cost Throttling
|
|
171
|
+
app.post('/api/checkout',
|
|
172
|
+
shield.strictBotGuard(),
|
|
173
|
+
shield.costThrottle({ weight: 5, maxCost: 50, windowMs: 60000 }),
|
|
174
|
+
(req, res) => {
|
|
175
|
+
res.json({ success: true, orderId: 'ord_123' });
|
|
176
|
+
}
|
|
177
|
+
);
|
|
226
178
|
|
|
227
|
-
|
|
179
|
+
// ── Protect High-Volume Reads with Audit Mode ──
|
|
180
|
+
// Does not reject searches, but logs kinematics and attaches X-Shield-Verdict header
|
|
181
|
+
app.get('/api/search', shield.behavioralGuard({ mode: 'audit' }), (req, res) => {
|
|
182
|
+
res.json({ results: [] });
|
|
183
|
+
});
|
|
184
|
+
```
|
|
228
185
|
|
|
229
|
-
|
|
186
|
+
### 2. Frontend Client Integration (`ShieldClient`)
|
|
230
187
|
|
|
231
|
-
|
|
188
|
+
Install `@chdlabs/my-shield` in your frontend application (or import `./client`).
|
|
232
189
|
|
|
233
|
-
|
|
190
|
+
The client captures human pointer micro-movements across desktop mice, trackpads, and mobile touchscreens (measuring both continuous trajectory gestures and button tap dwell kinetics). When `instrumentFetch()` is enabled, it automatically attaches the `x-shield-telemetry` header to your API requests:
|
|
234
191
|
|
|
235
192
|
```javascript
|
|
236
|
-
|
|
193
|
+
// client/index.js (React, Vue, or Vanilla JS)
|
|
194
|
+
import { ShieldClient } from '@chdlabs/my-shield/client';
|
|
237
195
|
|
|
238
|
-
// 1. Initialize
|
|
239
|
-
const
|
|
196
|
+
// 1. Initialize client SDK
|
|
197
|
+
const shieldClient = new ShieldClient({
|
|
198
|
+
sampleIntervalMs: 10,
|
|
240
199
|
windowLength: 128,
|
|
241
|
-
|
|
200
|
+
minPoints: 15, // Minimum raw events for continuous trajectories
|
|
201
|
+
maxIdleMs: 2500, // Freshness Guard: Discards trajectory if pointer idle > 2.5s
|
|
202
|
+
captureTouch: true, // Enables mobile touch & pointer dynamics
|
|
203
|
+
minTapDwellMs: 20, // Distinguishes genuine human taps from instant 0ms bot clicks
|
|
204
|
+
flushOnConsumption: true, // Prevents replaying identical trajectories
|
|
205
|
+
autoInjectTripwire: true // Injects invisible honeypot decoy trap into DOM
|
|
242
206
|
});
|
|
243
207
|
|
|
244
|
-
// 2. Start
|
|
245
|
-
|
|
208
|
+
// 2. Start recording kinematics
|
|
209
|
+
shieldClient.start();
|
|
246
210
|
|
|
247
|
-
// 3.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
211
|
+
// 3. Transparently instrument fetch (or Axios)
|
|
212
|
+
shieldClient.instrumentFetch({
|
|
213
|
+
headerName: 'x-shield-telemetry',
|
|
214
|
+
endpoints: ['/api/auth/*', '/api/checkout*']
|
|
215
|
+
});
|
|
251
216
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
217
|
+
// Now standard fetch calls automatically include kinematics telemetry!
|
|
218
|
+
async function submitLogin(email, password) {
|
|
219
|
+
try {
|
|
220
|
+
const res = await fetch('/api/auth/login', {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: { 'Content-Type': 'application/json' },
|
|
223
|
+
body: JSON.stringify({ email, password })
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (res.status === 403) {
|
|
227
|
+
const err = await res.json();
|
|
228
|
+
if (err.shieldCode === 'AUTOMATED_KINEMATICS_DETECTED') {
|
|
229
|
+
alert('Bot behavior detected. Please interact with the page normally.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
260
233
|
|
|
261
|
-
|
|
234
|
+
const data = await res.json();
|
|
235
|
+
console.log('Login successful:', data);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.error('Request failed:', err);
|
|
238
|
+
}
|
|
262
239
|
}
|
|
263
240
|
```
|
|
264
241
|
|
|
265
242
|
---
|
|
266
243
|
|
|
267
|
-
|
|
244
|
+
## 🌐 Enterprise Reverse Proxies & Gateway Protection
|
|
268
245
|
|
|
269
|
-
|
|
246
|
+
When deploying in Docker, Kubernetes, AWS, or behind Cloudflare, standard `req.ip` lookups fail or ban internal container gateways. `IPResolver` resolves this natively:
|
|
270
247
|
|
|
271
248
|
```javascript
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const app = express();
|
|
276
|
-
app.use(express.json());
|
|
249
|
+
const shield = createShield({
|
|
250
|
+
// Trust proxy headers: true | false | 'loopback,linklocal,uniquelocal'
|
|
251
|
+
trustProxy: true,
|
|
277
252
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// Protect sensitive endpoints with behavioral biometrics
|
|
281
|
-
app.post(
|
|
282
|
-
'/api/auth/login',
|
|
283
|
-
shield.behavioralGuard({
|
|
284
|
-
threshold: 0.70, // Minimum human probability required (default: 0.70)
|
|
285
|
-
headerName: 'x-shield-telemetry', // Header containing telemetry JSON
|
|
286
|
-
replayWindowMs: 15_000, // Max acceptable age/drift in ms (default: 15s)
|
|
287
|
-
autoBlockIP: true, // Auto-blacklist detected bots into Shield L1 Bloom + LRU
|
|
288
|
-
allowFallbackOnError: false, // Fail-closed (default: false)
|
|
289
|
-
onBotDetected: (req, res, meta) => {
|
|
290
|
-
// Optional custom callback when bot is intercepted
|
|
291
|
-
console.warn(`[BOT DETECTED] IP: ${req.ip}, Reason: ${meta.reason}, Confidence: ${meta.probability}`);
|
|
292
|
-
res.status(403).json({ error: 'Automated kinematics detected.', code: meta.reason });
|
|
293
|
-
}
|
|
294
|
-
}),
|
|
295
|
-
(req, res) => {
|
|
296
|
-
// Verified human request! Access biometric audit metadata:
|
|
297
|
-
console.log(req.shield.biometrics);
|
|
298
|
-
// => { isHuman: true, humanConfidence: 0.94, latencyMs: 0.85, score: 2.75 }
|
|
253
|
+
// Permanently protects Docker bridge (172.17.0.1) & loopbacks from bans
|
|
254
|
+
protectGateways: true,
|
|
299
255
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
256
|
+
// Optional custom subnets to shield from accidental bans
|
|
257
|
+
protectedSubnets: ['10.0.0.1', '192.168.1.1']
|
|
258
|
+
});
|
|
303
259
|
```
|
|
304
260
|
|
|
261
|
+
### Supported Proxy Headers (in priority order):
|
|
262
|
+
1. `cf-connecting-ip` (Cloudflare)
|
|
263
|
+
2. `true-client-ip` (Akamai & Cloudflare Enterprise)
|
|
264
|
+
3. `x-real-ip` (Nginx `proxy_set_header X-Real-IP $remote_addr`)
|
|
265
|
+
4. `x-client-ip`
|
|
266
|
+
5. `x-forwarded-for` (Multi-hop chains are parsed to find the first non-internal public IP)
|
|
267
|
+
|
|
268
|
+
If `trustProxy: false` is set (direct internet exposure without reverse proxy), all proxy headers are ignored to prevent header spoofing injection attacks.
|
|
269
|
+
|
|
305
270
|
---
|
|
306
271
|
|
|
307
|
-
|
|
272
|
+
## 💾 Pluggable Threat Persistence
|
|
308
273
|
|
|
309
|
-
|
|
274
|
+
In containerized or serverless deployments, in-memory caches reset on every restart. Shield supports pluggable persistence adapters to maintain active threat bans:
|
|
310
275
|
|
|
276
|
+
### 1. Single-Container File Persistence (Zero External Dependencies)
|
|
311
277
|
```javascript
|
|
312
|
-
|
|
278
|
+
const shield = createShield({
|
|
279
|
+
storage: {
|
|
280
|
+
type: 'file',
|
|
281
|
+
directory: './data/shield',
|
|
282
|
+
filename: 'blocked_ips.json',
|
|
283
|
+
debounceMs: 500 // Debounced atomic writes
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
await shield.init();
|
|
287
|
+
```
|
|
313
288
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
mr.loadThresholds(fittedThresholds);
|
|
317
|
-
const features = mr.transform(velocitySeries);
|
|
289
|
+
### 2. Database & Cache Persistence (PostgreSQL, Redis, MongoDB, Prisma)
|
|
290
|
+
Plug any database into Shield with three async callbacks:
|
|
318
291
|
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
292
|
+
```javascript
|
|
293
|
+
const shield = createShield({
|
|
294
|
+
storage: {
|
|
295
|
+
async onLoad() {
|
|
296
|
+
// Return active unexpired blocked IPs on boot
|
|
297
|
+
const { rows } = await db.query(
|
|
298
|
+
'SELECT ip, reason, metadata, expires_at AS "expiresAt" FROM blocked_threats WHERE expires_at IS NULL OR expires_at > NOW()'
|
|
299
|
+
);
|
|
300
|
+
return rows;
|
|
301
|
+
},
|
|
302
|
+
async onBlock(ip, reason, metadata, expiresAt) {
|
|
303
|
+
// Save or update blocked threat
|
|
304
|
+
await db.query(
|
|
305
|
+
`INSERT INTO blocked_threats (ip, reason, metadata, expires_at)
|
|
306
|
+
VALUES ($1, $2, $3, $4)
|
|
307
|
+
ON CONFLICT (ip) DO UPDATE SET reason = $2, metadata = $3, expires_at = $4`,
|
|
308
|
+
[ip, reason, JSON.stringify(metadata), expiresAt ? new Date(expiresAt) : null]
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
async onUnblock(ip) {
|
|
312
|
+
await db.query('DELETE FROM blocked_threats WHERE ip = $1', [ip]);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
323
316
|
|
|
324
|
-
|
|
325
|
-
|
|
317
|
+
// Restores unexpired database threats into Bloom filter and LRU cache
|
|
318
|
+
await shield.init();
|
|
326
319
|
```
|
|
327
320
|
|
|
328
321
|
---
|
|
329
322
|
|
|
330
|
-
|
|
323
|
+
## 🔑 Built-in Admin OTP / MFA Challenge Gate
|
|
331
324
|
|
|
332
|
-
|
|
325
|
+
Shield includes a standalone, zero-dependency dark-mode OTP challenge gate (`shield.otpGate()`) that protects `/admin/shield` without requiring external login libraries:
|
|
333
326
|
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
327
|
+
```javascript
|
|
328
|
+
app.use('/admin/shield', shield.otpGate({
|
|
329
|
+
adminEmail: 'admin@company.com',
|
|
330
|
+
jwtSecret: process.env.SHIELD_SECRET, // Signs session cookies
|
|
331
|
+
codeTtlMs: 5 * 60 * 1000, // Code valid for 5 minutes
|
|
332
|
+
maxAttempts: 5, // Locks out after 5 invalid tries
|
|
333
|
+
async sendOtp(email, code) {
|
|
334
|
+
// Deliver via email or SMS
|
|
335
|
+
await mailer.sendMail({
|
|
336
|
+
to: email,
|
|
337
|
+
subject: 'Shield Admin Passcode',
|
|
338
|
+
text: `Your one-time passcode is: ${code}`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}));
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
| `headerName` | `string` | `'x-shield-telemetry'` | Request header containing JSON telemetry payload |
|
|
347
|
-
| `replayWindowMs` | `number` | `15000` | Sliding window in ms; rejects expired or futuristic timestamps |
|
|
348
|
-
| `allowFallbackOnError` | `boolean` | `false` | Fail-open (`true`) vs fail-closed (`false`) when telemetry is missing or malformed |
|
|
349
|
-
| `autoBlockIP` | `boolean` | `false` | Automatically add detected bot IP to Shield L1 Bloom + LRU cache |
|
|
350
|
-
| `onBotDetected` | `Function` | `undefined` | Custom callback `(req, res, meta)` invoked when bot or replay is detected |
|
|
343
|
+
// Mount dashboard router
|
|
344
|
+
app.use('/admin/shield', shield.adminRouter());
|
|
345
|
+
```
|
|
351
346
|
|
|
352
347
|
---
|
|
353
348
|
|
|
@@ -356,26 +351,18 @@ This fits MiniRocket thresholds and Dual Ridge primal weights from your kinemati
|
|
|
356
351
|
| Option | Type | Default | Description |
|
|
357
352
|
|---|---|---|---|
|
|
358
353
|
| `whitelist` | `string[]` | `[]` | List of IP addresses that are never blocked |
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
354
|
+
| `trustProxy` | `boolean\|string` | `'loopback,linklocal,uniquelocal'` | Reverse proxy trust configuration |
|
|
355
|
+
| `protectGateways` | `boolean` | `true` | Protects Docker bridge (`172.17.0.1`) and loopback from bans |
|
|
356
|
+
| `storage` | `string\|Object` | `'memory'` | Storage adapter (`'memory'`, `'file'`, or callback hooks) |
|
|
357
|
+
| `admin.enabled` | `boolean` | `false` | Explicit switch to enable admin dashboard & telemetry |
|
|
358
|
+
| `bloom.expectedItems` | `number` | `100000` | Estimated distinct IPs to hold in Bloom filter |
|
|
361
359
|
| `bloom.falsePositiveRate` | `number` | `0.01` | Target false positive probability (1%) |
|
|
362
|
-
| `bloom.sizeBytes` | `number` | `null` | Explicit bit array size in bytes |
|
|
363
360
|
| `lru.maxSize` | `number` | `10000` | Max entries in confirmed threat cache |
|
|
364
361
|
| `lru.ttlMs` | `number` | `300000` | Threat cache TTL in ms (5 minutes) |
|
|
365
|
-
| `honeypot.enabled` | `boolean` | `true` | Enable/disable honeypot traps |
|
|
362
|
+
| `honeypot.enabled` | `boolean` | `true` | Enable/disable static honeypot traps |
|
|
366
363
|
| `honeypot.delayMs` | `number` | `500` | Delay before sending silent 200 OK decoy |
|
|
367
|
-
| `honeypot.additionalRoutes` | `string[]` | `[]` | Custom trap path patterns |
|
|
368
|
-
| `biometrics.modelWeights` | `Object` | `null` | Custom MiniRocket thresholds, weights, and bias |
|
|
369
364
|
| `intelligence.anomalyThreshold` | `number` | `5` | Unique 404s before IP is banned |
|
|
370
365
|
| `intelligence.anomalyWindowMs` | `number` | `60000` | Sliding window in ms (60 seconds) |
|
|
371
|
-
| `intelligence.filePersistence.enabled` | `boolean` | `true` | Enable periodic JSON snapshot saves |
|
|
372
|
-
| `intelligence.filePersistence.directory` | `string` | `'./data/shield'` | Destination directory for JSON snapshot |
|
|
373
|
-
| `intelligence.filePersistence.filename` | `string` | `'404_intelligence.json'` | Single deduplicated filename |
|
|
374
|
-
| `intelligence.filePersistence.intervalMs`| `number` | `3600000` | Snapshot save interval (1 hour) |
|
|
375
|
-
| `intelligence.filePersistence.loadOnStartup` | `boolean` | `true` | Restore intelligence on process start |
|
|
376
|
-
| `intelligence.autoPromote.enabled` | `boolean` | `true` | Auto-promote frequent 404s to traps |
|
|
377
|
-
| `intelligence.autoPromote.minHits` | `number` | `50` | Min 404 hits before promotion |
|
|
378
|
-
| `intelligence.autoPromote.minUniqueIPs` | `number` | `10` | Min distinct IPs probing path |
|
|
379
366
|
|
|
380
367
|
---
|
|
381
368
|
|
|
@@ -386,7 +373,6 @@ This fits MiniRocket thresholds and Dual Ridge primal weights from your kinemati
|
|
|
386
373
|
shield.blockIP('198.51.100.42', 'manual_ban', { admin: 'dev1' });
|
|
387
374
|
shield.unblockIP('198.51.100.42');
|
|
388
375
|
shield.allowIP('192.168.1.50'); // Add to whitelist
|
|
389
|
-
shield.removeAllowedIP('192.168.1.50');
|
|
390
376
|
shield.isWhitelisted('192.168.1.50'); // Returns boolean
|
|
391
377
|
|
|
392
378
|
// Telemetry & Metrics
|
|
@@ -394,14 +380,14 @@ shield.getStats(); // Returns aggregate stats, LRU sizes, traps list
|
|
|
394
380
|
shield.getIntelligence(); // Full 404 intelligence snapshot
|
|
395
381
|
shield.getBloomHealth(); // Saturation percentage and estimated FPR
|
|
396
382
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
app.
|
|
383
|
+
// Route-Level Bot Defenses
|
|
384
|
+
app.post('/api/auth/login', shield.strictBotGuard(), handler);
|
|
385
|
+
app.get('/api/search', shield.behavioralGuard({ mode: 'audit' }), handler);
|
|
400
386
|
|
|
401
|
-
//
|
|
387
|
+
// Denial-of-Wallet Cost Throttling
|
|
402
388
|
app.post('/api/ai-generate', shield.costThrottle({ weight: 10, maxCost: 100 }), handler);
|
|
403
389
|
|
|
404
|
-
//
|
|
390
|
+
// API Key / Secret Leak Detector
|
|
405
391
|
app.use(shield.keyLeakDetector());
|
|
406
392
|
```
|
|
407
393
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chdlabs/my-shield",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Zero-latency security shield middleware with Bloom filter, honeypots, 404 intelligence, and MiniRocket behavioral biometrics",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"./biometrics": {
|
|
18
18
|
"types": "./src/index.d.ts",
|
|
19
19
|
"import": "./src/biometrics/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./storage": {
|
|
22
|
+
"types": "./src/index.d.ts",
|
|
23
|
+
"import": "./src/storage/index.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"publishConfig": {
|