secure-api-guard 0.0.0-stage → 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 +22 -0
- package/README.md +549 -2
- package/package.json +64 -4
- package/src/config/defaults.js +98 -0
- package/src/config/validateConfig.js +148 -0
- package/src/index.js +132 -0
- package/src/middleware/apiKeyAuth.js +210 -0
- package/src/middleware/ipFilter.js +77 -0
- package/src/middleware/rateLimiter.js +129 -0
- package/src/middleware/requestSize.js +157 -0
- package/src/middleware/requestValidator.js +168 -0
- package/src/middleware/securityHeaders.js +59 -0
- package/src/middleware/threatDetector.js +132 -0
- package/src/security/apiKey.js +94 -0
- package/src/security/detector.js +161 -0
- package/src/security/ipAddress.js +224 -0
- package/src/stores/memoryStore.js +135 -0
- package/src/stores/redisStore.js +222 -0
- package/src/types/index.d.ts +313 -0
- package/src/utils/errors.js +88 -0
- package/src/utils/logger.js +229 -0
- package/src/utils/response.js +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 secure-api-guard contributors
|
|
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.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,550 @@
|
|
|
1
|
-
#
|
|
1
|
+
# secure-api-guard
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://expressjs.com)
|
|
6
|
+
|
|
7
|
+
> Production-ready, battle-tested security middleware suite for Node.js and Express applications.
|
|
8
|
+
|
|
9
|
+
`secure-api-guard` provides cohesive, modular security controls for modern Express APIs: distributed rate limiting, cryptographically secure API key authentication, CIDR-aware IP filtering, payload size protection, HTTP security headers, request validation, threat detection, and credential-safe security logging.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
1. [Package Introduction](#1-package-introduction)
|
|
16
|
+
2. [Features](#2-features)
|
|
17
|
+
3. [Installation Requirements](#3-installation-requirements)
|
|
18
|
+
4. [Installation](#4-installation)
|
|
19
|
+
5. [Quick Start](#5-quick-start)
|
|
20
|
+
6. [Individual Middleware Usage](#6-individual-middleware-usage)
|
|
21
|
+
- [A. Security Headers](#a-security-headers)
|
|
22
|
+
- [B. Rate Limiter](#b-rate-limiter)
|
|
23
|
+
- [C. API Key Authentication](#c-api-key-authentication)
|
|
24
|
+
- [D. IP Filtering](#d-ip-filtering)
|
|
25
|
+
- [E. Request Size Protection](#e-request-size-protection)
|
|
26
|
+
- [F. Request Validation (Zod & Custom)](#f-request-validation)
|
|
27
|
+
- [G. Threat Detection](#g-threat-detection)
|
|
28
|
+
- [H. Security Logging & Redaction](#h-security-logging--redaction)
|
|
29
|
+
7. [Redis Integration](#7-redis-integration)
|
|
30
|
+
8. [Configuration Options Reference](#8-configuration-options-reference)
|
|
31
|
+
9. [Error Response Format](#9-error-response-format)
|
|
32
|
+
10. [Security Considerations](#10-security-considerations)
|
|
33
|
+
11. [TypeScript Usage](#11-typescript-usage)
|
|
34
|
+
12. [Testing Instructions](#12-testing-instructions)
|
|
35
|
+
13. [Contributing](#13-contributing)
|
|
36
|
+
14. [License](#14-license)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 1. Package Introduction
|
|
41
|
+
|
|
42
|
+
Securing production Node.js APIs usually requires piecing together fragmented, incompatible libraries with inconsistent error formats, missing IPv6 support, or naive proxy handling.
|
|
43
|
+
|
|
44
|
+
`secure-api-guard` was designed from the ground up for high-security, high-throughput environments:
|
|
45
|
+
- **Express 4 & Express 5 Compatible**: Works seamlessly with both legacy and modern Express routing architectures.
|
|
46
|
+
- **Zero In-Memory Plaintext Secrets**: Compares and stores SHA-256 hashed keys in constant time.
|
|
47
|
+
- **Fail-Safe Proxy Handling**: Rejects `X-Forwarded-For` spoofing by default.
|
|
48
|
+
- **Standardized Error Structures**: Machine-readable error codes and consistent JSON payloads without leaking stack traces.
|
|
49
|
+
- **Zero Information Leakage**: Automatic deep redaction of credentials in application logs.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Features
|
|
54
|
+
|
|
55
|
+
- **Rate Limiting**: IP-based sliding/fixed window rate limiter with in-memory storage or multi-instance Redis cluster support (atomic Lua scripts).
|
|
56
|
+
- **API Key Authentication**: Cryptographically random key generation, constant-time hash verification, permission scoping, and expiration support.
|
|
57
|
+
- **IP Allowlisting & Blocklisting**: IPv4, IPv6, and CIDR subnets powered by `ipaddr.js`.
|
|
58
|
+
- **Request Size Defense**: Content-Length verification, HTTP request smuggling prevention, and stream chunk inspection.
|
|
59
|
+
- **Security Headers**: Hardened Helmet baseline with CSP, HSTS, frame protection, and search engine de-indexing.
|
|
60
|
+
- **Schema Validation**: Seamless, optional Zod integration with structured field-level error messages.
|
|
61
|
+
- **Threat Detection**: Heuristic detection for directory traversal, command injection, null-byte injection, and prototype pollution.
|
|
62
|
+
- **Security Logger**: Structured event audit trail with automatic deep redaction of passwords, tokens, cookies, and secret fields.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Installation Requirements
|
|
67
|
+
|
|
68
|
+
- **Runtime**: Node.js `20.0.0` or higher.
|
|
69
|
+
- **Framework**: Express `4.x` or Express `5.x`.
|
|
70
|
+
- **Module System**: CommonJS (`require` / `module.exports`). TypeScript definitions are included out of the box.
|
|
71
|
+
|
|
72
|
+
Optional peer dependencies:
|
|
73
|
+
- `zod` (`>= 3.0.0`): Required only when using Zod schema validation.
|
|
74
|
+
- `redis` (`>= 4.0.0`) or `ioredis` (`>= 5.0.0`): Required only when configuring Redis-backed rate limiting.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Core package
|
|
82
|
+
npm install secure-api-guard
|
|
83
|
+
|
|
84
|
+
# Optional: Add Zod if using schema validation
|
|
85
|
+
npm install zod
|
|
86
|
+
|
|
87
|
+
# Optional: Add ioredis or redis if using distributed rate limiting
|
|
88
|
+
npm install ioredis
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 5. Quick Start
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const express = require('express');
|
|
97
|
+
const {
|
|
98
|
+
securityHeaders,
|
|
99
|
+
rateLimiter,
|
|
100
|
+
requestSize,
|
|
101
|
+
apiKeyAuth
|
|
102
|
+
} = require('secure-api-guard');
|
|
103
|
+
|
|
104
|
+
const app = express();
|
|
105
|
+
|
|
106
|
+
// 1. Security Headers (CSP, HSTS, X-Content-Type-Options)
|
|
107
|
+
app.use(securityHeaders());
|
|
108
|
+
|
|
109
|
+
// 2. Global Rate Limiting (100 requests per 15 minutes per IP)
|
|
110
|
+
app.use(
|
|
111
|
+
rateLimiter({
|
|
112
|
+
windowMs: 15 * 60 * 1000,
|
|
113
|
+
maxRequests: 100
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// 3. Request Size Limiting (Reject bodies exceeding 1MB)
|
|
118
|
+
app.use(
|
|
119
|
+
requestSize({
|
|
120
|
+
maxSize: '1mb'
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 4. Protected Route with API Key Auth
|
|
125
|
+
app.get(
|
|
126
|
+
'/api/private',
|
|
127
|
+
apiKeyAuth({
|
|
128
|
+
keys: [process.env.ADMIN_API_KEY]
|
|
129
|
+
}),
|
|
130
|
+
(req, res) => {
|
|
131
|
+
res.json({
|
|
132
|
+
success: true,
|
|
133
|
+
message: 'Access granted'
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
app.listen(3000, () => {
|
|
139
|
+
console.log('Secure API running on http://localhost:3000');
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 6. Individual Middleware Usage
|
|
146
|
+
|
|
147
|
+
### A. Security Headers
|
|
148
|
+
|
|
149
|
+
Powered by Helmet with secure default headers tuned for APIs:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const { securityHeaders } = require('secure-api-guard');
|
|
153
|
+
|
|
154
|
+
app.use(
|
|
155
|
+
securityHeaders({
|
|
156
|
+
noIndex: true, // Adds X-Robots-Tag: noindex, nofollow, noarchive
|
|
157
|
+
contentSecurityPolicy: {
|
|
158
|
+
directives: {
|
|
159
|
+
defaultSrc: ["'none'"]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
hsts: {
|
|
163
|
+
maxAge: 31536000,
|
|
164
|
+
includeSubDomains: true,
|
|
165
|
+
preload: true
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### B. Rate Limiter
|
|
172
|
+
|
|
173
|
+
Implements standard `RateLimit-*` (RFC draft-7) headers and returns HTTP 429 when limits are exceeded.
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const { rateLimiter } = require('secure-api-guard');
|
|
177
|
+
|
|
178
|
+
// Route-specific strict limiter (e.g. Login endpoint)
|
|
179
|
+
const loginLimiter = rateLimiter({
|
|
180
|
+
windowMs: 60 * 1000, // 1 minute
|
|
181
|
+
maxRequests: 5,
|
|
182
|
+
message: 'Too many login attempts, please try again later.',
|
|
183
|
+
keyGenerator: (req) => req.body.email || req.ip,
|
|
184
|
+
skip: (req) => req.ip === '127.0.0.1' // Skip local testing
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
app.post('/api/auth/login', loginLimiter, (req, res) => {
|
|
188
|
+
res.json({ success: true });
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### C. API Key Authentication
|
|
193
|
+
|
|
194
|
+
Supports cryptographic key generation, constant-time verification (`crypto.timingSafeEqual`), expiration checks, and role permissions.
|
|
195
|
+
|
|
196
|
+
#### Key Generation Utility
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const { generateApiKey } = require('secure-api-guard');
|
|
200
|
+
|
|
201
|
+
const { key, hash, prefix } = generateApiKey({
|
|
202
|
+
prefix: 'sag_live_',
|
|
203
|
+
bytes: 32
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
console.log('Provide key to client:', key);
|
|
207
|
+
console.log('Store this hash in database:', hash);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Middleware Verification
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
const { apiKeyAuth } = require('secure-api-guard');
|
|
214
|
+
|
|
215
|
+
app.use(
|
|
216
|
+
'/api/v1',
|
|
217
|
+
apiKeyAuth({
|
|
218
|
+
headerName: 'x-api-key', // default: 'x-api-key'
|
|
219
|
+
allowBearer: true, // Also allows "Authorization: Bearer <key>"
|
|
220
|
+
// Lookup key by SHA-256 hash in database
|
|
221
|
+
getKey: async (hash, req) => {
|
|
222
|
+
const record = await db.apiKeys.findOne({ hash });
|
|
223
|
+
if (!record) return null;
|
|
224
|
+
return {
|
|
225
|
+
id: record.id,
|
|
226
|
+
name: record.clientName,
|
|
227
|
+
hash: record.hash,
|
|
228
|
+
expiresAt: record.expiresAt,
|
|
229
|
+
permissions: record.scopes
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
requiredPermissions: ['read:orders']
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
app.get('/api/v1/orders', (req, res) => {
|
|
237
|
+
// Safe metadata attached; raw key is NEVER attached to req.apiKey
|
|
238
|
+
console.log('Caller ID:', req.apiKey.id);
|
|
239
|
+
res.json({ orders: [] });
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### D. IP Filtering
|
|
244
|
+
|
|
245
|
+
Filter requests by IPv4, IPv6, or CIDR blocks.
|
|
246
|
+
|
|
247
|
+
> **Security Warning on Proxies**: Never blindly trust `X-Forwarded-For`. Unless `trustProxy` is explicitly configured with your trusted load balancers/proxies, `secure-api-guard` only inspects the direct network socket IP address.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const { ipFilter } = require('secure-api-guard');
|
|
251
|
+
|
|
252
|
+
// Allowlist mode: only specified subnet can access
|
|
253
|
+
app.use(
|
|
254
|
+
'/admin',
|
|
255
|
+
ipFilter({
|
|
256
|
+
mode: 'allowlist',
|
|
257
|
+
allowlist: [
|
|
258
|
+
'127.0.0.1',
|
|
259
|
+
'::1',
|
|
260
|
+
'10.0.0.0/8', // Private IPv4 CIDR
|
|
261
|
+
'2001:db8::/32' // IPv6 CIDR
|
|
262
|
+
],
|
|
263
|
+
// Only trust X-Forwarded-For if request came from our trusted proxy IP
|
|
264
|
+
trustProxy: ['172.16.0.10']
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### E. Request Size Protection
|
|
270
|
+
|
|
271
|
+
Protects against memory exhaustion and HTTP request smuggling before body parsers allocate memory.
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const { requestSize } = require('secure-api-guard');
|
|
275
|
+
|
|
276
|
+
// Must be placed before express.json()
|
|
277
|
+
app.use(
|
|
278
|
+
requestSize({
|
|
279
|
+
maxSize: '500kb',
|
|
280
|
+
message: 'Payload Too Large: Maximum allowed body is 500KB.'
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
app.use(express.json());
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### F. Request Validation
|
|
288
|
+
|
|
289
|
+
Integrates seamlessly with [Zod](https://github.com/colinhacks/zod) to validate `body`, `query`, and `params`.
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const { z } = require('zod');
|
|
293
|
+
const { requestValidator } = require('secure-api-guard');
|
|
294
|
+
|
|
295
|
+
const userSchema = z.object({
|
|
296
|
+
name: z.string().min(2).max(50),
|
|
297
|
+
email: z.string().email(),
|
|
298
|
+
age: z.number().int().positive().optional()
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const querySchema = z.object({
|
|
302
|
+
page: z.string().regex(/^\d+$/).transform(Number)
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
app.post(
|
|
306
|
+
'/users',
|
|
307
|
+
requestValidator({
|
|
308
|
+
body: userSchema,
|
|
309
|
+
query: querySchema
|
|
310
|
+
}),
|
|
311
|
+
(req, res) => {
|
|
312
|
+
// Validated, type-safe data accessible on req.validated
|
|
313
|
+
const { name, email } = req.validated.body;
|
|
314
|
+
const { page } = req.validated.query;
|
|
315
|
+
|
|
316
|
+
res.status(201).json({ success: true, user: { name, email, page } });
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### G. Threat Detection
|
|
322
|
+
|
|
323
|
+
Heuristic inspection for suspicious attack patterns in URLs, query strings, headers, and request bodies.
|
|
324
|
+
|
|
325
|
+
> **Defense-in-Depth Notice**: Threat pattern detection is a heuristic defense layer. It is not an alternative to parameterized SQL queries or contextual HTML encoding.
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
const { threatDetector } = require('secure-api-guard');
|
|
329
|
+
|
|
330
|
+
app.use(
|
|
331
|
+
threatDetector({
|
|
332
|
+
action: 'reject', // 'reject' (403) | 'log' (logs warning) | 'pass'
|
|
333
|
+
whitelistedPaths: ['/webhooks/incoming'],
|
|
334
|
+
whitelistedKeys: ['markdown', 'articleContent'], // Prevent false positives on rich text
|
|
335
|
+
customRules: [
|
|
336
|
+
{
|
|
337
|
+
id: 'CUSTOM_SIGNATURE',
|
|
338
|
+
name: 'Block specific attack string',
|
|
339
|
+
pattern: /malicious_pattern_regex/i,
|
|
340
|
+
severity: 'high'
|
|
341
|
+
}
|
|
342
|
+
]
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### H. Security Logging & Redaction
|
|
348
|
+
|
|
349
|
+
Record security events without ever leaking credentials or secrets:
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const { SecurityLogger, redact } = require('secure-api-guard');
|
|
353
|
+
|
|
354
|
+
const logger = new SecurityLogger({
|
|
355
|
+
level: 'info',
|
|
356
|
+
customLogger: (event) => {
|
|
357
|
+
// Pipe to Winston, Pino, Datadog, CloudWatch, etc.
|
|
358
|
+
myWinstonLogger.warn(event.message, event);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Deep redaction utility
|
|
363
|
+
const sanitized = redact({
|
|
364
|
+
user: 'alice',
|
|
365
|
+
password: 'mySecretPassword',
|
|
366
|
+
tokens: [{ token: 'jwt-access-token' }]
|
|
367
|
+
});
|
|
368
|
+
// Result: { user: 'alice', password: '[REDACTED]', tokens: [{ token: '[REDACTED]' }] }
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## 7. Redis Integration
|
|
374
|
+
|
|
375
|
+
For multi-instance deployments running behind Kubernetes, AWS ECS, or horizontal auto-scalers, use `RedisStore` to ensure atomic distributed rate limiting across all instances without race conditions.
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
const Redis = require('ioredis');
|
|
379
|
+
const { rateLimiter, RedisStore } = require('secure-api-guard');
|
|
380
|
+
|
|
381
|
+
const redisClient = new Redis(process.env.REDIS_URL);
|
|
382
|
+
|
|
383
|
+
app.use(
|
|
384
|
+
rateLimiter({
|
|
385
|
+
windowMs: 60 * 1000,
|
|
386
|
+
maxRequests: 100,
|
|
387
|
+
store: 'redis',
|
|
388
|
+
redisClient,
|
|
389
|
+
passOnRedisError: true // Fail-open: avoids bringing down API if Redis cluster degrades
|
|
390
|
+
})
|
|
391
|
+
);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 8. Configuration Options Reference
|
|
397
|
+
|
|
398
|
+
### `rateLimiter(options)`
|
|
399
|
+
|
|
400
|
+
| Option | Type | Default | Description |
|
|
401
|
+
|---|---|---|---|
|
|
402
|
+
| `windowMs` | `number` | `900000` (15m) | Rate limiting window in milliseconds. |
|
|
403
|
+
| `maxRequests` | `number` | `100` | Maximum allowed hits per client in the window. |
|
|
404
|
+
| `keyGenerator` | `Function` | `(req) => req.ip` | Custom client identification function. |
|
|
405
|
+
| `skip` | `Function` | `() => false` | Condition to skip rate limiting for a request. |
|
|
406
|
+
| `store` | `'memory' \| 'redis' \| object` | `'memory'` | Storage adapter instance. |
|
|
407
|
+
| `redisClient` | `object` | `undefined` | Redis client instance (`ioredis` or `node-redis`). |
|
|
408
|
+
| `passOnRedisError` | `boolean` | `true` | Fail-open if Redis encounters connection errors. |
|
|
409
|
+
| `statusCode` | `number` | `429` | HTTP status code when limit exceeded. |
|
|
410
|
+
| `message` | `string` | `'Too many requests...'` | Rejection message. |
|
|
411
|
+
|
|
412
|
+
### `apiKeyAuth(options)`
|
|
413
|
+
|
|
414
|
+
| Option | Type | Default | Description |
|
|
415
|
+
|---|---|---|---|
|
|
416
|
+
| `headerName` | `string` | `'x-api-key'` | Custom header to read API key from. |
|
|
417
|
+
| `allowBearer` | `boolean` | `true` | Support `Authorization: Bearer <key>` header. |
|
|
418
|
+
| `queryParam` | `string \| null` | `null` | Allowed query parameter (disabled by default for security). |
|
|
419
|
+
| `keys` | `Array<string \| object>` | `[]` | Static list of keys or hashed key records. |
|
|
420
|
+
| `getKey` | `Function` | `undefined` | Async lookup function `(hash, req) => Promise<record>`. |
|
|
421
|
+
| `requiredPermissions` | `string[]` | `[]` | Permissions required on the key to proceed. |
|
|
422
|
+
| `statusCode` | `number` | `401` | Status code on missing or invalid key. |
|
|
423
|
+
| `forbiddenStatusCode` | `number` | `403` | Status code on expired key or missing permissions. |
|
|
424
|
+
|
|
425
|
+
### `ipFilter(options)`
|
|
426
|
+
|
|
427
|
+
| Option | Type | Default | Description |
|
|
428
|
+
|---|---|---|---|
|
|
429
|
+
| `mode` | `'allowlist' \| 'blocklist'` | `'blocklist'` | Filtering strategy. |
|
|
430
|
+
| `allowlist` | `string[]` | `[]` | List of allowed IP addresses or CIDR ranges. |
|
|
431
|
+
| `blocklist` | `string[]` | `[]` | List of blocked IP addresses or CIDR ranges. |
|
|
432
|
+
| `trustProxy` | `boolean \| string[] \| number` | `false` | Proxy trust policy. |
|
|
433
|
+
| `getIp` | `Function` | `undefined` | Custom IP resolver function. |
|
|
434
|
+
| `statusCode` | `number` | `403` | Status code on rejection. |
|
|
435
|
+
|
|
436
|
+
### `requestSize(options)`
|
|
437
|
+
|
|
438
|
+
| Option | Type | Default | Description |
|
|
439
|
+
|---|---|---|---|
|
|
440
|
+
| `maxSize` | `string \| number` | `'1mb'` | Maximum payload size (bytes or e.g. `'500kb'`). |
|
|
441
|
+
| `statusCode` | `number` | `413` | Status code on limit exceed. |
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 9. Error Response Format
|
|
446
|
+
|
|
447
|
+
All security rejections adhere to a uniform, clean JSON envelope:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"success": false,
|
|
452
|
+
"error": {
|
|
453
|
+
"code": "RATE_LIMIT_EXCEEDED",
|
|
454
|
+
"message": "Too many requests, please try again later.",
|
|
455
|
+
"details": {
|
|
456
|
+
"limit": 100,
|
|
457
|
+
"windowMs": 900000,
|
|
458
|
+
"retryAfter": 60
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Validation errors include granular field-level issues:
|
|
465
|
+
|
|
466
|
+
```json
|
|
467
|
+
{
|
|
468
|
+
"success": false,
|
|
469
|
+
"error": {
|
|
470
|
+
"code": "VALIDATION_ERROR",
|
|
471
|
+
"message": "Validation failed: Invalid request parameters.",
|
|
472
|
+
"details": {
|
|
473
|
+
"issues": [
|
|
474
|
+
{
|
|
475
|
+
"field": "body.email",
|
|
476
|
+
"message": "Invalid email address",
|
|
477
|
+
"code": "invalid_string"
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 10. Security Considerations
|
|
488
|
+
|
|
489
|
+
1. **Timing Attack Protection**: API key comparison uses `crypto.timingSafeEqual` over fixed-length cryptographic hashes to neutralize timing side channels.
|
|
490
|
+
2. **Hash at Rest**: Never store API keys in plaintext. Store the SHA-256 hash in your database, and provide only the raw key to your end users once upon creation.
|
|
491
|
+
3. **Never Blindly Trust `X-Forwarded-For`**: Attackers can spoof `X-Forwarded-For: 127.0.0.1`. Leave `trustProxy: false` unless running behind a reverse proxy whose direct IP you have verified.
|
|
492
|
+
4. **Defense in Depth**: Heuristic threat detection catches low-hanging exploits (e.g. `../../etc/passwd`), but you must always use parameterized database queries and validate inputs with schemas.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 11. TypeScript Usage
|
|
497
|
+
|
|
498
|
+
Full TypeScript declarations are bundled in `src/types/index.d.ts`.
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import express, { Request, Response } from 'express';
|
|
502
|
+
import { rateLimiter, apiKeyAuth, RequestValidatorOptions } from 'secure-api-guard';
|
|
503
|
+
|
|
504
|
+
const app = express();
|
|
505
|
+
|
|
506
|
+
app.get(
|
|
507
|
+
'/api/secure-profile',
|
|
508
|
+
apiKeyAuth(),
|
|
509
|
+
(req: Request, res: Response) => {
|
|
510
|
+
// req.apiKey is strongly typed
|
|
511
|
+
const callerId = req.apiKey?.id;
|
|
512
|
+
res.json({ callerId });
|
|
513
|
+
}
|
|
514
|
+
);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## 12. Testing Instructions
|
|
520
|
+
|
|
521
|
+
The package contains an automated test suite with Vitest and Supertest.
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
# Run unit and integration tests
|
|
525
|
+
npm test
|
|
526
|
+
|
|
527
|
+
# Run tests with code coverage analysis
|
|
528
|
+
npm run test:coverage
|
|
529
|
+
|
|
530
|
+
# Run tests in watch mode
|
|
531
|
+
npm run test:watch
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 13. Contributing
|
|
537
|
+
|
|
538
|
+
1. Fork the repository and create your feature branch: `git checkout -b feature/my-feature`.
|
|
539
|
+
2. Ensure all tests pass: `npm test`.
|
|
540
|
+
3. Verify test coverage: `npm run test:coverage`.
|
|
541
|
+
4. Commit your changes: `git commit -m 'feat: add feature'`.
|
|
542
|
+
5. Push to the branch: `git push origin feature/my-feature`.
|
|
543
|
+
6. Open a Pull Request.
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## 14. License
|
|
548
|
+
|
|
549
|
+
Distributed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
2
550
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "secure-api-guard",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready, battle-tested security middleware suite for Node.js and Express applications",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/types/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"test:watch": "vitest",
|
|
10
|
+
"test:coverage": "vitest run --coverage"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"security",
|
|
14
|
+
"express",
|
|
15
|
+
"middleware",
|
|
16
|
+
"rate-limit",
|
|
17
|
+
"api-key",
|
|
18
|
+
"ip-filter",
|
|
19
|
+
"helmet",
|
|
20
|
+
"security-headers",
|
|
21
|
+
"threat-detection",
|
|
22
|
+
"request-size",
|
|
23
|
+
"request-validation"
|
|
24
|
+
],
|
|
25
|
+
"author": "secure-api-guard contributors",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"type": "commonjs",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"bytes": "^3.1.2",
|
|
38
|
+
"express-rate-limit": "^8.7.3",
|
|
39
|
+
"helmet": "^8.3.0",
|
|
40
|
+
"ipaddr.js": "^2.5.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
44
|
+
"express": "^5.3.0",
|
|
45
|
+
"ioredis": "^6.0.0",
|
|
46
|
+
"supertest": "^7.3.1",
|
|
47
|
+
"vitest": "^4.1.12",
|
|
48
|
+
"zod": "^4.6.7"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"redis": ">=4.0.0",
|
|
52
|
+
"ioredis": ">=5.0.0",
|
|
53
|
+
"zod": ">=3.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"redis": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"ioredis": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"zod": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|