@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.
Files changed (39) hide show
  1. package/.agent/rules/api-design.md +649 -0
  2. package/.agent/rules/architecture.md +2507 -0
  3. package/.agent/rules/best-practices.md +622 -0
  4. package/.agent/rules/code-style.md +308 -0
  5. package/.agent/rules/design-patterns.md +577 -0
  6. package/.agent/rules/devops.md +230 -0
  7. package/.agent/rules/error-handling.md +417 -0
  8. package/.agent/rules/instructions.md +28 -0
  9. package/.agent/rules/language.md +786 -0
  10. package/.agent/rules/performance.md +710 -0
  11. package/.agent/rules/security.md +587 -0
  12. package/.agent/rules/testing.md +572 -0
  13. package/.agent/workflows/add-documentation.md +10 -0
  14. package/.agent/workflows/generate-integration-tests.md +10 -0
  15. package/.agent/workflows/generate-unit-tests.md +11 -0
  16. package/.agent/workflows/performance-audit.md +11 -0
  17. package/.agent/workflows/refactor-extract-module.md +12 -0
  18. package/.agent/workflows/security-audit.md +12 -0
  19. package/.gemini/instructions.md +4843 -0
  20. package/AGENTS.md +9 -11
  21. package/bun.lock +755 -4
  22. package/claude.md +2 -2
  23. package/config.example.yml +129 -0
  24. package/config.yml +38 -0
  25. package/data/guideline-mappings.yml +128 -0
  26. package/data/language/dart/async.md +289 -0
  27. package/data/language/dart/basics.md +280 -0
  28. package/data/language/dart/error-handling.md +355 -0
  29. package/data/language/dart/index.md +10 -0
  30. package/data/language/dart/testing.md +352 -0
  31. package/data/language/swift/basics.md +477 -0
  32. package/data/language/swift/concurrency.md +654 -0
  33. package/data/language/swift/error-handling.md +679 -0
  34. package/data/language/swift/swiftui-mvvm.md +795 -0
  35. package/data/language/swift/testing.md +708 -0
  36. package/data/version.json +10 -8
  37. package/dist/index.js +50295 -29101
  38. package/jest.config.js +46 -0
  39. 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*