@aicgen/aicgen 1.0.0-beta.2 → 1.0.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/.agent/rules/api-design.md +649 -0
- package/.agent/rules/architecture.md +2507 -0
- package/.agent/rules/best-practices.md +622 -0
- package/.agent/rules/code-style.md +308 -0
- package/.agent/rules/design-patterns.md +577 -0
- package/.agent/rules/devops.md +230 -0
- package/.agent/rules/error-handling.md +417 -0
- package/.agent/rules/instructions.md +28 -0
- package/.agent/rules/language.md +786 -0
- package/.agent/rules/performance.md +710 -0
- package/.agent/rules/security.md +587 -0
- package/.agent/rules/testing.md +572 -0
- package/.agent/workflows/add-documentation.md +10 -0
- package/.agent/workflows/generate-integration-tests.md +10 -0
- package/.agent/workflows/generate-unit-tests.md +11 -0
- package/.agent/workflows/performance-audit.md +11 -0
- package/.agent/workflows/refactor-extract-module.md +12 -0
- package/.agent/workflows/security-audit.md +12 -0
- package/.gemini/instructions.md +4843 -0
- package/AGENTS.md +9 -11
- package/bun.lock +755 -4
- package/claude.md +2 -2
- package/config.example.yml +129 -0
- package/config.yml +38 -0
- package/data/guideline-mappings.yml +128 -0
- package/data/language/dart/async.md +289 -0
- package/data/language/dart/basics.md +280 -0
- package/data/language/dart/error-handling.md +355 -0
- package/data/language/dart/index.md +10 -0
- package/data/language/dart/testing.md +352 -0
- package/data/language/swift/basics.md +477 -0
- package/data/language/swift/concurrency.md +654 -0
- package/data/language/swift/error-handling.md +679 -0
- package/data/language/swift/swiftui-mvvm.md +795 -0
- package/data/language/swift/testing.md +708 -0
- package/data/version.json +10 -8
- package/dist/index.js +50295 -29101
- package/jest.config.js +46 -0
- package/package.json +13 -2
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
# Injection Prevention
|
|
4
|
+
|
|
5
|
+
## SQL Injection Prevention
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// ❌ DANGEROUS: String concatenation
|
|
9
|
+
const getUserByEmail = async (email: string) => {
|
|
10
|
+
const query = `SELECT * FROM users WHERE email = '${email}'`;
|
|
11
|
+
// Input: ' OR '1'='1
|
|
12
|
+
// Result: SELECT * FROM users WHERE email = '' OR '1'='1'
|
|
13
|
+
return db.query(query);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ✅ SAFE: Parameterized queries
|
|
17
|
+
const getUserByEmail = async (email: string) => {
|
|
18
|
+
return db.query('SELECT * FROM users WHERE email = ?', [email]);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ✅ SAFE: Using ORM
|
|
22
|
+
const getUserByEmail = async (email: string) => {
|
|
23
|
+
return userRepository.findOne({ where: { email } });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ✅ SAFE: Query builder
|
|
27
|
+
const getUsers = async (minAge: number) => {
|
|
28
|
+
return db
|
|
29
|
+
.select('*')
|
|
30
|
+
.from('users')
|
|
31
|
+
.where('age', '>', minAge); // Automatically parameterized
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## NoSQL Injection Prevention
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// ❌ DANGEROUS: Accepting objects from user input
|
|
39
|
+
app.post('/login', (req, res) => {
|
|
40
|
+
const { username, password } = req.body;
|
|
41
|
+
// If password = {$gt: ""}, it bypasses password check!
|
|
42
|
+
db.users.findOne({ username, password });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ✅ SAFE: Validate input types
|
|
46
|
+
app.post('/login', (req, res) => {
|
|
47
|
+
const { username, password } = req.body;
|
|
48
|
+
|
|
49
|
+
if (typeof username !== 'string' || typeof password !== 'string') {
|
|
50
|
+
throw new Error('Invalid input types');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
db.users.findOne({ username, password });
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Command Injection Prevention
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// ❌ DANGEROUS: Shell command with user input
|
|
61
|
+
const convertImage = async (filename: string) => {
|
|
62
|
+
exec(`convert ${filename} output.jpg`);
|
|
63
|
+
// Input: "file.png; rm -rf /"
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ✅ SAFE: Use arrays, avoid shell
|
|
67
|
+
import { execFile } from 'child_process';
|
|
68
|
+
|
|
69
|
+
const convertImage = async (filename: string) => {
|
|
70
|
+
execFile('convert', [filename, 'output.jpg']);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ✅ SAFE: Validate input against whitelist
|
|
74
|
+
const allowedFilename = /^[a-zA-Z0-9_-]+\.(png|jpg|gif)$/;
|
|
75
|
+
if (!allowedFilename.test(filename)) {
|
|
76
|
+
throw new Error('Invalid filename');
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Path Traversal Prevention
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ❌ DANGEROUS: Direct path usage
|
|
84
|
+
app.get('/files/:filename', (req, res) => {
|
|
85
|
+
res.sendFile(`/uploads/${req.params.filename}`);
|
|
86
|
+
// Input: ../../etc/passwd
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ✅ SAFE: Validate and normalize path
|
|
90
|
+
import path from 'path';
|
|
91
|
+
|
|
92
|
+
app.get('/files/:filename', (req, res) => {
|
|
93
|
+
const safeName = path.basename(req.params.filename);
|
|
94
|
+
const filePath = path.join('/uploads', safeName);
|
|
95
|
+
const normalizedPath = path.normalize(filePath);
|
|
96
|
+
|
|
97
|
+
if (!normalizedPath.startsWith('/uploads/')) {
|
|
98
|
+
return res.status(400).json({ error: 'Invalid filename' });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
res.sendFile(normalizedPath);
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Input Validation
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// ✅ Whitelist validation
|
|
109
|
+
import { z } from 'zod';
|
|
110
|
+
|
|
111
|
+
const userSchema = z.object({
|
|
112
|
+
email: z.string().email(),
|
|
113
|
+
password: z.string().min(12).max(160),
|
|
114
|
+
age: z.number().int().min(0).max(150),
|
|
115
|
+
role: z.enum(['user', 'admin'])
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const validateUser = (data: unknown) => {
|
|
119
|
+
return userSchema.parse(data);
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
# Authentication & JWT Security
|
|
127
|
+
|
|
128
|
+
## Password Storage
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import bcrypt from 'bcrypt';
|
|
132
|
+
|
|
133
|
+
const SALT_ROUNDS = 12; // Work factor
|
|
134
|
+
|
|
135
|
+
// ✅ Hash password with bcrypt
|
|
136
|
+
async function hashPassword(password: string): Promise<string> {
|
|
137
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
141
|
+
return bcrypt.compare(password, hash);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ✅ Validate password strength
|
|
145
|
+
function validatePassword(password: string): void {
|
|
146
|
+
if (password.length < 12) {
|
|
147
|
+
throw new Error('Password must be at least 12 characters');
|
|
148
|
+
}
|
|
149
|
+
if (password.length > 160) {
|
|
150
|
+
throw new Error('Password too long'); // Prevent DoS via bcrypt
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## JWT Best Practices
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import jwt from 'jsonwebtoken';
|
|
159
|
+
|
|
160
|
+
const JWT_SECRET = process.env.JWT_SECRET!;
|
|
161
|
+
const ACCESS_TOKEN_EXPIRY = '15m';
|
|
162
|
+
const REFRESH_TOKEN_EXPIRY = '7d';
|
|
163
|
+
|
|
164
|
+
// ✅ Generate tokens
|
|
165
|
+
function generateTokens(userId: string) {
|
|
166
|
+
const accessToken = jwt.sign(
|
|
167
|
+
{ sub: userId, type: 'access' },
|
|
168
|
+
JWT_SECRET,
|
|
169
|
+
{ expiresIn: ACCESS_TOKEN_EXPIRY }
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const refreshToken = jwt.sign(
|
|
173
|
+
{ sub: userId, type: 'refresh' },
|
|
174
|
+
JWT_SECRET,
|
|
175
|
+
{ expiresIn: REFRESH_TOKEN_EXPIRY }
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return { accessToken, refreshToken };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ✅ Verify and decode token
|
|
182
|
+
function verifyToken(token: string) {
|
|
183
|
+
try {
|
|
184
|
+
return jwt.verify(token, JWT_SECRET);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof jwt.TokenExpiredError) {
|
|
187
|
+
throw new UnauthorizedError('Token expired');
|
|
188
|
+
}
|
|
189
|
+
throw new UnauthorizedError('Invalid token');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Login Protection
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import rateLimit from 'express-rate-limit';
|
|
198
|
+
|
|
199
|
+
// ✅ Rate limit login attempts
|
|
200
|
+
const loginLimiter = rateLimit({
|
|
201
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
202
|
+
max: 5, // 5 attempts
|
|
203
|
+
message: 'Too many login attempts, please try again later',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
app.post('/login', loginLimiter, async (req, res) => {
|
|
207
|
+
const { email, password } = req.body;
|
|
208
|
+
|
|
209
|
+
const user = await userService.findByEmail(email);
|
|
210
|
+
|
|
211
|
+
// ✅ Generic error message (don't reveal if user exists)
|
|
212
|
+
if (!user || !await verifyPassword(password, user.passwordHash)) {
|
|
213
|
+
return res.status(401).json({ error: 'Invalid email or password' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const tokens = generateTokens(user.id);
|
|
217
|
+
|
|
218
|
+
// Regenerate session to prevent fixation
|
|
219
|
+
req.session.regenerate(() => {
|
|
220
|
+
res.json({ ...tokens });
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Session Security
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
app.use(session({
|
|
229
|
+
secret: process.env.SESSION_SECRET!,
|
|
230
|
+
name: 'sessionId', // Don't use default 'connect.sid'
|
|
231
|
+
|
|
232
|
+
cookie: {
|
|
233
|
+
secure: true, // HTTPS only
|
|
234
|
+
httpOnly: true, // Prevent XSS access
|
|
235
|
+
sameSite: 'strict', // CSRF protection
|
|
236
|
+
maxAge: 30 * 60 * 1000, // 30 minutes
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
resave: false,
|
|
240
|
+
saveUninitialized: false,
|
|
241
|
+
store: new RedisStore({ client: redisClient })
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
// ✅ Session regeneration after login
|
|
245
|
+
app.post('/login', async (req, res, next) => {
|
|
246
|
+
// ... authenticate user ...
|
|
247
|
+
|
|
248
|
+
req.session.regenerate((err) => {
|
|
249
|
+
req.session.userId = user.id;
|
|
250
|
+
res.json({ success: true });
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Authorization Middleware
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// ✅ Require authentication
|
|
259
|
+
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
|
|
260
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
261
|
+
|
|
262
|
+
if (!token) {
|
|
263
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const payload = verifyToken(token);
|
|
268
|
+
req.user = await userService.findById(payload.sub);
|
|
269
|
+
next();
|
|
270
|
+
} catch (error) {
|
|
271
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// ✅ Require specific role
|
|
276
|
+
const requireRole = (...roles: string[]) => {
|
|
277
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
278
|
+
if (!roles.includes(req.user.role)) {
|
|
279
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
280
|
+
}
|
|
281
|
+
next();
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
# Secrets Management
|
|
290
|
+
|
|
291
|
+
## Environment Variables
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// ❌ NEVER hardcode secrets
|
|
295
|
+
const config = {
|
|
296
|
+
dbPassword: 'super_secret_password',
|
|
297
|
+
apiKey: 'sk-1234567890abcdef'
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// ✅ Use environment variables
|
|
301
|
+
import dotenv from 'dotenv';
|
|
302
|
+
dotenv.config();
|
|
303
|
+
|
|
304
|
+
const config = {
|
|
305
|
+
dbPassword: process.env.DB_PASSWORD,
|
|
306
|
+
apiKey: process.env.API_KEY,
|
|
307
|
+
sessionSecret: process.env.SESSION_SECRET
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Validate Required Secrets
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// ✅ Fail fast if secrets missing
|
|
315
|
+
const requiredEnvVars = [
|
|
316
|
+
'DB_PASSWORD',
|
|
317
|
+
'API_KEY',
|
|
318
|
+
'SESSION_SECRET',
|
|
319
|
+
'JWT_SECRET'
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
requiredEnvVars.forEach(varName => {
|
|
323
|
+
if (!process.env[varName]) {
|
|
324
|
+
throw new Error(`Missing required environment variable: ${varName}`);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// ✅ Type-safe config
|
|
329
|
+
interface Config {
|
|
330
|
+
dbPassword: string;
|
|
331
|
+
apiKey: string;
|
|
332
|
+
sessionSecret: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function loadConfig(): Config {
|
|
336
|
+
const dbPassword = process.env.DB_PASSWORD;
|
|
337
|
+
if (!dbPassword) throw new Error('DB_PASSWORD required');
|
|
338
|
+
|
|
339
|
+
// ... validate all required vars
|
|
340
|
+
|
|
341
|
+
return { dbPassword, apiKey, sessionSecret };
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Generate Strong Secrets
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# Generate cryptographically secure secrets
|
|
349
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
|
350
|
+
|
|
351
|
+
# Or using OpenSSL
|
|
352
|
+
openssl rand -base64 32
|
|
353
|
+
|
|
354
|
+
# Or using head
|
|
355
|
+
head -c32 /dev/urandom | base64
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## .gitignore Configuration
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# .gitignore - NEVER commit secrets
|
|
362
|
+
.env
|
|
363
|
+
.env.local
|
|
364
|
+
.env.*.local
|
|
365
|
+
*.key
|
|
366
|
+
*.pem
|
|
367
|
+
secrets/
|
|
368
|
+
credentials.json
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Environment Example File
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# .env.example - commit this to show required variables
|
|
375
|
+
DB_HOST=localhost
|
|
376
|
+
DB_PORT=5432
|
|
377
|
+
DB_NAME=myapp
|
|
378
|
+
DB_USER=
|
|
379
|
+
DB_PASSWORD=
|
|
380
|
+
|
|
381
|
+
API_KEY=
|
|
382
|
+
SESSION_SECRET=
|
|
383
|
+
JWT_SECRET=
|
|
384
|
+
|
|
385
|
+
# Copy to .env and fill in actual values
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Secrets in CI/CD
|
|
389
|
+
|
|
390
|
+
```yaml
|
|
391
|
+
# GitHub Actions
|
|
392
|
+
- name: Deploy
|
|
393
|
+
env:
|
|
394
|
+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
395
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
396
|
+
run: ./deploy.sh
|
|
397
|
+
|
|
398
|
+
# ❌ Never echo secrets in logs
|
|
399
|
+
- name: Configure
|
|
400
|
+
run: |
|
|
401
|
+
echo "Configuring application..."
|
|
402
|
+
# echo "DB_PASSWORD=$DB_PASSWORD" # NEVER do this!
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Secrets Rotation
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// ✅ Support for rotating secrets
|
|
409
|
+
class SecretManager {
|
|
410
|
+
async getSecret(name: string): Promise<string> {
|
|
411
|
+
// Check for new secret first (during rotation)
|
|
412
|
+
const newSecret = process.env[`${name}_NEW`];
|
|
413
|
+
if (newSecret) {
|
|
414
|
+
return newSecret;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const secret = process.env[name];
|
|
418
|
+
if (!secret) {
|
|
419
|
+
throw new Error(`Secret ${name} not found`);
|
|
420
|
+
}
|
|
421
|
+
return secret;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ✅ Accept multiple JWT signing keys during rotation
|
|
426
|
+
function verifyToken(token: string) {
|
|
427
|
+
const keys = [process.env.JWT_SECRET, process.env.JWT_SECRET_OLD].filter(Boolean);
|
|
428
|
+
|
|
429
|
+
for (const key of keys) {
|
|
430
|
+
try {
|
|
431
|
+
return jwt.verify(token, key);
|
|
432
|
+
} catch {}
|
|
433
|
+
}
|
|
434
|
+
throw new Error('Invalid token');
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
# Security Headers
|
|
442
|
+
|
|
443
|
+
## Essential Headers with Helmet
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import helmet from 'helmet';
|
|
447
|
+
|
|
448
|
+
// ✅ Apply security headers with sensible defaults
|
|
449
|
+
app.use(helmet());
|
|
450
|
+
|
|
451
|
+
// ✅ Custom configuration
|
|
452
|
+
app.use(helmet({
|
|
453
|
+
contentSecurityPolicy: {
|
|
454
|
+
directives: {
|
|
455
|
+
defaultSrc: ["'self'"],
|
|
456
|
+
scriptSrc: ["'self'", "'unsafe-inline'"],
|
|
457
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
458
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
hsts: {
|
|
462
|
+
maxAge: 31536000,
|
|
463
|
+
includeSubDomains: true,
|
|
464
|
+
preload: true
|
|
465
|
+
}
|
|
466
|
+
}));
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Manual Header Configuration
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
app.use((req, res, next) => {
|
|
473
|
+
// Prevent MIME sniffing
|
|
474
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
475
|
+
|
|
476
|
+
// Prevent clickjacking
|
|
477
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
478
|
+
|
|
479
|
+
// XSS protection
|
|
480
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
481
|
+
|
|
482
|
+
// Force HTTPS
|
|
483
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
484
|
+
|
|
485
|
+
// Referrer policy
|
|
486
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
487
|
+
|
|
488
|
+
// Permissions policy
|
|
489
|
+
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
|
490
|
+
|
|
491
|
+
next();
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Content Security Policy (CSP)
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// ✅ Strict CSP for maximum protection
|
|
499
|
+
res.setHeader('Content-Security-Policy', [
|
|
500
|
+
"default-src 'self'",
|
|
501
|
+
"script-src 'self'",
|
|
502
|
+
"style-src 'self' 'unsafe-inline'",
|
|
503
|
+
"img-src 'self' data: https:",
|
|
504
|
+
"font-src 'self'",
|
|
505
|
+
"connect-src 'self' https://api.example.com",
|
|
506
|
+
"frame-ancestors 'none'",
|
|
507
|
+
"form-action 'self'"
|
|
508
|
+
].join('; '));
|
|
509
|
+
|
|
510
|
+
// For APIs that don't serve HTML
|
|
511
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'");
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## CORS Configuration
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import cors from 'cors';
|
|
518
|
+
|
|
519
|
+
// ✅ Configure CORS properly
|
|
520
|
+
app.use(cors({
|
|
521
|
+
origin: ['https://example.com', 'https://app.example.com'],
|
|
522
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
523
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
524
|
+
credentials: true,
|
|
525
|
+
maxAge: 86400 // Cache preflight for 24 hours
|
|
526
|
+
}));
|
|
527
|
+
|
|
528
|
+
// ❌ Never use in production
|
|
529
|
+
app.use(cors({ origin: '*' })); // Allows any origin
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## HTTPS Enforcement
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// ✅ Redirect HTTP to HTTPS
|
|
536
|
+
app.use((req, res, next) => {
|
|
537
|
+
if (!req.secure && req.get('x-forwarded-proto') !== 'https') {
|
|
538
|
+
return res.redirect(301, `https://${req.hostname}${req.url}`);
|
|
539
|
+
}
|
|
540
|
+
next();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// ✅ HSTS header (included in helmet)
|
|
544
|
+
res.setHeader(
|
|
545
|
+
'Strict-Transport-Security',
|
|
546
|
+
'max-age=31536000; includeSubDomains; preload'
|
|
547
|
+
);
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Cookie Security
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// ✅ Secure cookie settings
|
|
554
|
+
app.use(session({
|
|
555
|
+
cookie: {
|
|
556
|
+
secure: true, // Only send over HTTPS
|
|
557
|
+
httpOnly: true, // Not accessible via JavaScript
|
|
558
|
+
sameSite: 'strict', // CSRF protection
|
|
559
|
+
maxAge: 30 * 60 * 1000
|
|
560
|
+
}
|
|
561
|
+
}));
|
|
562
|
+
|
|
563
|
+
// ✅ Set secure cookies manually
|
|
564
|
+
res.cookie('token', value, {
|
|
565
|
+
httpOnly: true,
|
|
566
|
+
secure: process.env.NODE_ENV === 'production',
|
|
567
|
+
sameSite: 'strict',
|
|
568
|
+
maxAge: 3600000
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Header Checklist
|
|
573
|
+
|
|
574
|
+
```
|
|
575
|
+
✅ X-Content-Type-Options: nosniff
|
|
576
|
+
✅ X-Frame-Options: DENY
|
|
577
|
+
✅ X-XSS-Protection: 1; mode=block
|
|
578
|
+
✅ Strict-Transport-Security: max-age=31536000
|
|
579
|
+
✅ Content-Security-Policy: (appropriate policy)
|
|
580
|
+
✅ Referrer-Policy: strict-origin-when-cross-origin
|
|
581
|
+
✅ Permissions-Policy: restrict unused features
|
|
582
|
+
✅ Secure, HttpOnly, SameSite cookies
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
*Generated by aicgen*
|