omgkit 2.1.0 → 2.2.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.
Files changed (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,57 +1,918 @@
1
1
  ---
2
2
  name: owasp
3
- description: OWASP security. Use for security best practices, vulnerability prevention.
3
+ description: OWASP security best practices for web applications with vulnerability prevention, secure coding, and security testing
4
+ category: security
5
+ triggers:
6
+ - owasp
7
+ - web security
8
+ - security best practices
9
+ - vulnerability prevention
10
+ - secure coding
11
+ - penetration testing
4
12
  ---
5
13
 
6
- # OWASP Skill
14
+ # OWASP
7
15
 
8
- ## Top 10 Prevention
16
+ Enterprise-grade **web application security** following OWASP best practices. This skill covers the OWASP Top 10 vulnerabilities, secure coding patterns, input validation, authentication security, and security testing patterns used by top engineering teams.
17
+
18
+ ## Purpose
19
+
20
+ Build secure web applications:
21
+
22
+ - Prevent OWASP Top 10 vulnerabilities
23
+ - Implement secure coding practices
24
+ - Validate and sanitize user input
25
+ - Protect against injection attacks
26
+ - Secure authentication and sessions
27
+ - Configure security headers
28
+ - Implement security testing
29
+
30
+ ## Features
31
+
32
+ ### 1. Injection Prevention
9
33
 
10
- ### 1. Injection
11
34
  ```typescript
12
- // Bad
13
- db.query(`SELECT * FROM users WHERE id = ${id}`);
35
+ // lib/security/sql-injection.ts
36
+ import { PrismaClient } from "@prisma/client";
37
+
38
+ const prisma = new PrismaClient();
39
+
40
+ // BAD: SQL Injection vulnerable
41
+ async function unsafeQuery(userId: string) {
42
+ // NEVER DO THIS
43
+ return prisma.$queryRawUnsafe(
44
+ `SELECT * FROM users WHERE id = '${userId}'`
45
+ );
46
+ }
47
+
48
+ // GOOD: Parameterized queries
49
+ async function safeQuery(userId: string) {
50
+ return prisma.user.findUnique({
51
+ where: { id: userId },
52
+ });
53
+ }
54
+
55
+ // GOOD: Raw query with parameters
56
+ async function safeRawQuery(userId: string) {
57
+ return prisma.$queryRaw`
58
+ SELECT * FROM users WHERE id = ${userId}
59
+ `;
60
+ }
61
+
62
+ // lib/security/nosql-injection.ts
63
+ import { Filter } from "mongodb";
64
+
65
+ interface User {
66
+ _id: string;
67
+ email: string;
68
+ password: string;
69
+ }
70
+
71
+ // BAD: NoSQL Injection vulnerable
72
+ async function unsafeMongoQuery(db: Db, email: unknown) {
73
+ // If email is { $gt: "" }, this returns all users
74
+ return db.collection("users").findOne({ email });
75
+ }
76
+
77
+ // GOOD: Type validation before query
78
+ async function safeMongoQuery(db: Db, email: unknown) {
79
+ if (typeof email !== "string") {
80
+ throw new Error("Invalid email format");
81
+ }
82
+
83
+ const filter: Filter<User> = { email };
84
+ return db.collection<User>("users").findOne(filter);
85
+ }
86
+
87
+ // lib/security/command-injection.ts
88
+ import { execFile } from "child_process";
89
+ import { promisify } from "util";
90
+
91
+ const execFileAsync = promisify(execFile);
92
+
93
+ // BAD: Command Injection vulnerable
94
+ async function unsafeExec(filename: string) {
95
+ const { exec } = await import("child_process");
96
+ // NEVER DO THIS
97
+ exec(`ls -la ${filename}`, (error, stdout) => {
98
+ console.log(stdout);
99
+ });
100
+ }
101
+
102
+ // GOOD: Use execFile with arguments array
103
+ async function safeExec(filename: string) {
104
+ // Validate filename
105
+ if (!/^[\w\-. ]+$/.test(filename)) {
106
+ throw new Error("Invalid filename");
107
+ }
108
+
109
+ const { stdout } = await execFileAsync("ls", ["-la", filename]);
110
+ return stdout;
111
+ }
14
112
 
15
- // Good
16
- db.query('SELECT * FROM users WHERE id = $1', [id]);
113
+ // GOOD: Avoid shell commands entirely
114
+ import fs from "fs/promises";
115
+
116
+ async function listFiles(directory: string) {
117
+ // Validate and resolve path
118
+ const resolvedPath = path.resolve(directory);
119
+ const basePath = path.resolve("/allowed/base/path");
120
+
121
+ if (!resolvedPath.startsWith(basePath)) {
122
+ throw new Error("Path traversal attempt detected");
123
+ }
124
+
125
+ return fs.readdir(resolvedPath, { withFileTypes: true });
126
+ }
17
127
  ```
18
128
 
19
- ### 2. Broken Authentication
129
+ ### 2. XSS Prevention
130
+
20
131
  ```typescript
21
- // Use secure session management
22
- // Implement MFA
23
- // Rate limit login attempts
132
+ // lib/security/xss.ts
133
+ import DOMPurify from "isomorphic-dompurify";
134
+ import { escape } from "html-escaper";
135
+
136
+ // Sanitize HTML content
137
+ export function sanitizeHtml(dirty: string): string {
138
+ return DOMPurify.sanitize(dirty, {
139
+ ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br", "ul", "ol", "li"],
140
+ ALLOWED_ATTR: ["href", "target", "rel"],
141
+ ALLOW_DATA_ATTR: false,
142
+ });
143
+ }
144
+
145
+ // Escape for text content
146
+ export function escapeHtml(text: string): string {
147
+ return escape(text);
148
+ }
149
+
150
+ // Safe URL validation
151
+ export function isValidUrl(url: string): boolean {
152
+ try {
153
+ const parsed = new URL(url);
154
+ return ["http:", "https:"].includes(parsed.protocol);
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+
160
+ // React component with XSS prevention
161
+ import React from "react";
162
+
163
+ interface UserContentProps {
164
+ content: string;
165
+ allowHtml?: boolean;
166
+ }
167
+
168
+ export function UserContent({ content, allowHtml = false }: UserContentProps) {
169
+ if (allowHtml) {
170
+ // Sanitize before rendering
171
+ const sanitized = sanitizeHtml(content);
172
+ return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
173
+ }
174
+
175
+ // Default: escape all HTML
176
+ return <div>{content}</div>;
177
+ }
178
+
179
+ // Express middleware for XSS protection
180
+ import { Request, Response, NextFunction } from "express";
181
+
182
+ export function xssProtection(
183
+ req: Request,
184
+ res: Response,
185
+ next: NextFunction
186
+ ) {
187
+ // Sanitize request body
188
+ if (req.body && typeof req.body === "object") {
189
+ req.body = sanitizeObject(req.body);
190
+ }
191
+
192
+ // Sanitize query parameters
193
+ if (req.query && typeof req.query === "object") {
194
+ req.query = sanitizeObject(req.query as Record<string, unknown>);
195
+ }
196
+
197
+ next();
198
+ }
199
+
200
+ function sanitizeObject(obj: Record<string, unknown>): Record<string, unknown> {
201
+ const sanitized: Record<string, unknown> = {};
202
+
203
+ for (const [key, value] of Object.entries(obj)) {
204
+ if (typeof value === "string") {
205
+ sanitized[key] = escapeHtml(value);
206
+ } else if (typeof value === "object" && value !== null) {
207
+ sanitized[key] = sanitizeObject(value as Record<string, unknown>);
208
+ } else {
209
+ sanitized[key] = value;
210
+ }
211
+ }
212
+
213
+ return sanitized;
214
+ }
24
215
  ```
25
216
 
26
- ### 3. XSS Prevention
217
+ ### 3. CSRF Protection
218
+
27
219
  ```typescript
28
- // Bad
29
- element.innerHTML = userInput;
220
+ // lib/security/csrf.ts
221
+ import crypto from "crypto";
222
+ import { Request, Response, NextFunction } from "express";
30
223
 
31
- // Good
32
- element.textContent = userInput;
33
- // Or use DOMPurify
224
+ const CSRF_TOKEN_LENGTH = 32;
225
+ const CSRF_HEADER = "x-csrf-token";
226
+ const CSRF_COOKIE = "csrf_token";
227
+
228
+ // Generate CSRF token
229
+ export function generateCsrfToken(): string {
230
+ return crypto.randomBytes(CSRF_TOKEN_LENGTH).toString("hex");
231
+ }
232
+
233
+ // CSRF middleware
234
+ export function csrfProtection() {
235
+ return (req: Request, res: Response, next: NextFunction) => {
236
+ // Skip for safe methods
237
+ if (["GET", "HEAD", "OPTIONS"].includes(req.method)) {
238
+ return next();
239
+ }
240
+
241
+ const cookieToken = req.cookies[CSRF_COOKIE];
242
+ const headerToken = req.headers[CSRF_HEADER];
243
+
244
+ if (!cookieToken || !headerToken || cookieToken !== headerToken) {
245
+ return res.status(403).json({
246
+ error: "CSRF validation failed",
247
+ message: "Invalid or missing CSRF token",
248
+ });
249
+ }
250
+
251
+ next();
252
+ };
253
+ }
254
+
255
+ // Set CSRF token on response
256
+ export function setCsrfToken(req: Request, res: Response, next: NextFunction) {
257
+ if (!req.cookies[CSRF_COOKIE]) {
258
+ const token = generateCsrfToken();
259
+ res.cookie(CSRF_COOKIE, token, {
260
+ httpOnly: false, // Must be readable by JavaScript
261
+ secure: process.env.NODE_ENV === "production",
262
+ sameSite: "strict",
263
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
264
+ });
265
+ }
266
+ next();
267
+ }
268
+
269
+ // React hook for CSRF
270
+ export function useCsrf() {
271
+ const getToken = (): string | null => {
272
+ const match = document.cookie.match(new RegExp(`${CSRF_COOKIE}=([^;]+)`));
273
+ return match ? match[1] : null;
274
+ };
275
+
276
+ const fetchWithCsrf = async (url: string, options: RequestInit = {}) => {
277
+ const token = getToken();
278
+
279
+ return fetch(url, {
280
+ ...options,
281
+ headers: {
282
+ ...options.headers,
283
+ [CSRF_HEADER]: token || "",
284
+ },
285
+ });
286
+ };
287
+
288
+ return { getToken, fetchWithCsrf };
289
+ }
34
290
  ```
35
291
 
36
- ### 4. CSRF Prevention
292
+ ### 4. Authentication Security
293
+
37
294
  ```typescript
38
- // Use CSRF tokens
39
- // SameSite cookies
40
- // Verify Origin header
295
+ // lib/security/password.ts
296
+ import bcrypt from "bcrypt";
297
+ import crypto from "crypto";
298
+
299
+ const SALT_ROUNDS = 12;
300
+ const MIN_PASSWORD_LENGTH = 12;
301
+ const MAX_PASSWORD_LENGTH = 128;
302
+
303
+ export interface PasswordValidationResult {
304
+ valid: boolean;
305
+ errors: string[];
306
+ }
307
+
308
+ // Password validation
309
+ export function validatePassword(password: string): PasswordValidationResult {
310
+ const errors: string[] = [];
311
+
312
+ if (password.length < MIN_PASSWORD_LENGTH) {
313
+ errors.push(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
314
+ }
315
+
316
+ if (password.length > MAX_PASSWORD_LENGTH) {
317
+ errors.push(`Password must be at most ${MAX_PASSWORD_LENGTH} characters`);
318
+ }
319
+
320
+ if (!/[a-z]/.test(password)) {
321
+ errors.push("Password must contain at least one lowercase letter");
322
+ }
323
+
324
+ if (!/[A-Z]/.test(password)) {
325
+ errors.push("Password must contain at least one uppercase letter");
326
+ }
327
+
328
+ if (!/\d/.test(password)) {
329
+ errors.push("Password must contain at least one digit");
330
+ }
331
+
332
+ if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
333
+ errors.push("Password must contain at least one special character");
334
+ }
335
+
336
+ // Check for common passwords
337
+ if (isCommonPassword(password)) {
338
+ errors.push("Password is too common");
339
+ }
340
+
341
+ return { valid: errors.length === 0, errors };
342
+ }
343
+
344
+ // Hash password
345
+ export async function hashPassword(password: string): Promise<string> {
346
+ return bcrypt.hash(password, SALT_ROUNDS);
347
+ }
348
+
349
+ // Verify password
350
+ export async function verifyPassword(
351
+ password: string,
352
+ hash: string
353
+ ): Promise<boolean> {
354
+ return bcrypt.compare(password, hash);
355
+ }
356
+
357
+ // Generate secure token
358
+ export function generateSecureToken(length = 32): string {
359
+ return crypto.randomBytes(length).toString("hex");
360
+ }
361
+
362
+ // lib/security/rate-limit.ts
363
+ import rateLimit from "express-rate-limit";
364
+ import RedisStore from "rate-limit-redis";
365
+ import Redis from "ioredis";
366
+
367
+ const redis = new Redis(process.env.REDIS_URL);
368
+
369
+ // Login rate limiting
370
+ export const loginRateLimiter = rateLimit({
371
+ store: new RedisStore({
372
+ sendCommand: (...args: string[]) => redis.call(...args),
373
+ }),
374
+ windowMs: 15 * 60 * 1000, // 15 minutes
375
+ max: 5, // 5 attempts per window
376
+ message: {
377
+ error: "Too many login attempts",
378
+ message: "Please try again after 15 minutes",
379
+ },
380
+ standardHeaders: true,
381
+ legacyHeaders: false,
382
+ keyGenerator: (req) => {
383
+ // Rate limit by IP and email combination
384
+ return `${req.ip}:${req.body?.email || "unknown"}`;
385
+ },
386
+ });
387
+
388
+ // API rate limiting
389
+ export const apiRateLimiter = rateLimit({
390
+ store: new RedisStore({
391
+ sendCommand: (...args: string[]) => redis.call(...args),
392
+ }),
393
+ windowMs: 60 * 1000, // 1 minute
394
+ max: 100, // 100 requests per minute
395
+ message: {
396
+ error: "Too many requests",
397
+ message: "Please slow down",
398
+ },
399
+ standardHeaders: true,
400
+ legacyHeaders: false,
401
+ });
402
+
403
+ // lib/security/session.ts
404
+ import session from "express-session";
405
+ import RedisStore from "connect-redis";
406
+
407
+ export function configureSession(redis: Redis) {
408
+ return session({
409
+ store: new RedisStore({ client: redis }),
410
+ name: "session_id",
411
+ secret: process.env.SESSION_SECRET!,
412
+ resave: false,
413
+ saveUninitialized: false,
414
+ cookie: {
415
+ secure: process.env.NODE_ENV === "production",
416
+ httpOnly: true,
417
+ sameSite: "strict",
418
+ maxAge: 24 * 60 * 60 * 1000, // 24 hours
419
+ domain: process.env.COOKIE_DOMAIN,
420
+ },
421
+ rolling: true, // Reset expiry on activity
422
+ });
423
+ }
41
424
  ```
42
425
 
43
426
  ### 5. Security Headers
427
+
44
428
  ```typescript
45
- app.use(helmet({
46
- contentSecurityPolicy: true,
47
- hsts: true,
48
- noSniff: true,
49
- }));
429
+ // lib/security/headers.ts
430
+ import helmet from "helmet";
431
+ import { Express } from "express";
432
+
433
+ export function configureSecurityHeaders(app: Express) {
434
+ // Use helmet with custom configuration
435
+ app.use(
436
+ helmet({
437
+ // Content Security Policy
438
+ contentSecurityPolicy: {
439
+ directives: {
440
+ defaultSrc: ["'self'"],
441
+ scriptSrc: ["'self'", "'strict-dynamic'"],
442
+ styleSrc: ["'self'", "'unsafe-inline'"], // Required for many CSS-in-JS
443
+ imgSrc: ["'self'", "data:", "https:"],
444
+ fontSrc: ["'self'", "https://fonts.gstatic.com"],
445
+ connectSrc: ["'self'", process.env.API_URL],
446
+ frameSrc: ["'none'"],
447
+ objectSrc: ["'none'"],
448
+ baseUri: ["'self'"],
449
+ formAction: ["'self'"],
450
+ frameAncestors: ["'none'"],
451
+ upgradeInsecureRequests: [],
452
+ },
453
+ },
454
+
455
+ // Strict Transport Security
456
+ strictTransportSecurity: {
457
+ maxAge: 31536000, // 1 year
458
+ includeSubDomains: true,
459
+ preload: true,
460
+ },
461
+
462
+ // Prevent clickjacking
463
+ frameguard: {
464
+ action: "deny",
465
+ },
466
+
467
+ // Prevent MIME sniffing
468
+ noSniff: true,
469
+
470
+ // XSS filter (legacy browsers)
471
+ xssFilter: true,
472
+
473
+ // Referrer policy
474
+ referrerPolicy: {
475
+ policy: "strict-origin-when-cross-origin",
476
+ },
477
+
478
+ // Permissions policy
479
+ permittedCrossDomainPolicies: {
480
+ permittedPolicies: "none",
481
+ },
482
+ })
483
+ );
484
+
485
+ // Additional security headers
486
+ app.use((req, res, next) => {
487
+ // Prevent caching of sensitive data
488
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
489
+ res.setHeader("Pragma", "no-cache");
490
+
491
+ // Remove server information
492
+ res.removeHeader("X-Powered-By");
493
+
494
+ next();
495
+ });
496
+ }
497
+
498
+ // Next.js security headers
499
+ // next.config.js
500
+ const securityHeaders = [
501
+ {
502
+ key: "X-DNS-Prefetch-Control",
503
+ value: "on",
504
+ },
505
+ {
506
+ key: "Strict-Transport-Security",
507
+ value: "max-age=31536000; includeSubDomains; preload",
508
+ },
509
+ {
510
+ key: "X-Frame-Options",
511
+ value: "DENY",
512
+ },
513
+ {
514
+ key: "X-Content-Type-Options",
515
+ value: "nosniff",
516
+ },
517
+ {
518
+ key: "X-XSS-Protection",
519
+ value: "1; mode=block",
520
+ },
521
+ {
522
+ key: "Referrer-Policy",
523
+ value: "strict-origin-when-cross-origin",
524
+ },
525
+ {
526
+ key: "Permissions-Policy",
527
+ value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
528
+ },
529
+ {
530
+ key: "Content-Security-Policy",
531
+ value: `
532
+ default-src 'self';
533
+ script-src 'self' 'unsafe-eval' 'unsafe-inline';
534
+ style-src 'self' 'unsafe-inline';
535
+ img-src 'self' data: https:;
536
+ font-src 'self';
537
+ connect-src 'self' ${process.env.NEXT_PUBLIC_API_URL};
538
+ frame-ancestors 'none';
539
+ base-uri 'self';
540
+ form-action 'self';
541
+ `.replace(/\s+/g, " ").trim(),
542
+ },
543
+ ];
544
+
545
+ module.exports = {
546
+ async headers() {
547
+ return [
548
+ {
549
+ source: "/:path*",
550
+ headers: securityHeaders,
551
+ },
552
+ ];
553
+ },
554
+ };
50
555
  ```
51
556
 
52
- ## Checklist
53
- - [ ] Input validation
54
- - [ ] Output encoding
55
- - [ ] Parameterized queries
56
- - [ ] Secure headers
57
- - [ ] HTTPS only
557
+ ### 6. Input Validation
558
+
559
+ ```typescript
560
+ // lib/security/validation.ts
561
+ import { z } from "zod";
562
+
563
+ // Email validation schema
564
+ export const emailSchema = z
565
+ .string()
566
+ .email("Invalid email format")
567
+ .max(254, "Email too long")
568
+ .transform((email) => email.toLowerCase().trim());
569
+
570
+ // Password validation schema
571
+ export const passwordSchema = z
572
+ .string()
573
+ .min(12, "Password must be at least 12 characters")
574
+ .max(128, "Password must be at most 128 characters")
575
+ .regex(/[a-z]/, "Password must contain a lowercase letter")
576
+ .regex(/[A-Z]/, "Password must contain an uppercase letter")
577
+ .regex(/\d/, "Password must contain a digit")
578
+ .regex(/[!@#$%^&*]/, "Password must contain a special character");
579
+
580
+ // User registration schema
581
+ export const registerSchema = z.object({
582
+ email: emailSchema,
583
+ password: passwordSchema,
584
+ name: z
585
+ .string()
586
+ .min(2, "Name must be at least 2 characters")
587
+ .max(100, "Name must be at most 100 characters")
588
+ .regex(/^[\p{L}\s'-]+$/u, "Name contains invalid characters"),
589
+ });
590
+
591
+ // URL validation
592
+ export const urlSchema = z
593
+ .string()
594
+ .url("Invalid URL format")
595
+ .refine(
596
+ (url) => {
597
+ try {
598
+ const parsed = new URL(url);
599
+ return ["http:", "https:"].includes(parsed.protocol);
600
+ } catch {
601
+ return false;
602
+ }
603
+ },
604
+ { message: "URL must use http or https protocol" }
605
+ );
606
+
607
+ // File upload validation
608
+ export const fileUploadSchema = z.object({
609
+ filename: z
610
+ .string()
611
+ .max(255)
612
+ .regex(
613
+ /^[\w\-. ]+$/,
614
+ "Filename contains invalid characters"
615
+ ),
616
+ mimetype: z.enum([
617
+ "image/jpeg",
618
+ "image/png",
619
+ "image/gif",
620
+ "application/pdf",
621
+ ]),
622
+ size: z.number().max(10 * 1024 * 1024, "File size must be under 10MB"),
623
+ });
624
+
625
+ // Express validation middleware
626
+ import { Request, Response, NextFunction } from "express";
627
+
628
+ export function validate<T>(schema: z.ZodSchema<T>) {
629
+ return async (req: Request, res: Response, next: NextFunction) => {
630
+ try {
631
+ req.body = await schema.parseAsync(req.body);
632
+ next();
633
+ } catch (error) {
634
+ if (error instanceof z.ZodError) {
635
+ return res.status(400).json({
636
+ error: "Validation failed",
637
+ details: error.errors.map((e) => ({
638
+ field: e.path.join("."),
639
+ message: e.message,
640
+ })),
641
+ });
642
+ }
643
+ next(error);
644
+ }
645
+ };
646
+ }
647
+
648
+ // Usage
649
+ app.post("/register", validate(registerSchema), async (req, res) => {
650
+ // req.body is now typed and validated
651
+ const { email, password, name } = req.body;
652
+ // ...
653
+ });
654
+ ```
655
+
656
+ ### 7. Security Testing
657
+
658
+ ```typescript
659
+ // tests/security/xss.test.ts
660
+ import { sanitizeHtml, escapeHtml } from "@/lib/security/xss";
661
+
662
+ describe("XSS Prevention", () => {
663
+ describe("sanitizeHtml", () => {
664
+ it("removes script tags", () => {
665
+ const input = '<script>alert("xss")</script>';
666
+ expect(sanitizeHtml(input)).toBe("");
667
+ });
668
+
669
+ it("removes event handlers", () => {
670
+ const input = '<img src="x" onerror="alert(1)">';
671
+ expect(sanitizeHtml(input)).toBe("");
672
+ });
673
+
674
+ it("removes javascript: URLs", () => {
675
+ const input = '<a href="javascript:alert(1)">click</a>';
676
+ const result = sanitizeHtml(input);
677
+ expect(result).not.toContain("javascript:");
678
+ });
679
+
680
+ it("allows safe HTML tags", () => {
681
+ const input = "<p><strong>Bold</strong> and <em>italic</em></p>";
682
+ expect(sanitizeHtml(input)).toBe(input);
683
+ });
684
+
685
+ it("removes data attributes", () => {
686
+ const input = '<div data-dangerous="value">content</div>';
687
+ expect(sanitizeHtml(input)).not.toContain("data-dangerous");
688
+ });
689
+ });
690
+
691
+ describe("escapeHtml", () => {
692
+ it("escapes HTML entities", () => {
693
+ const input = '<script>alert("xss")</script>';
694
+ const result = escapeHtml(input);
695
+ expect(result).toBe("&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;");
696
+ });
697
+
698
+ it("escapes ampersands", () => {
699
+ expect(escapeHtml("&")).toBe("&amp;");
700
+ });
701
+ });
702
+ });
703
+
704
+ // tests/security/injection.test.ts
705
+ import { safeQuery, safeRawQuery } from "@/lib/security/sql-injection";
706
+
707
+ describe("SQL Injection Prevention", () => {
708
+ it("handles malicious input safely", async () => {
709
+ const maliciousInput = "'; DROP TABLE users; --";
710
+
711
+ // Should not throw and should not execute injection
712
+ await expect(safeQuery(maliciousInput)).resolves.toBeNull();
713
+ });
714
+
715
+ it("uses parameterized queries", async () => {
716
+ const userId = "123";
717
+ const result = await safeRawQuery(userId);
718
+
719
+ // Query should be parameterized, not interpolated
720
+ expect(result).toBeDefined();
721
+ });
722
+ });
723
+
724
+ // tests/security/csrf.test.ts
725
+ import request from "supertest";
726
+ import app from "@/app";
727
+
728
+ describe("CSRF Protection", () => {
729
+ it("rejects requests without CSRF token", async () => {
730
+ const response = await request(app)
731
+ .post("/api/user/profile")
732
+ .send({ name: "Test" });
733
+
734
+ expect(response.status).toBe(403);
735
+ expect(response.body.error).toBe("CSRF validation failed");
736
+ });
737
+
738
+ it("accepts requests with valid CSRF token", async () => {
739
+ // Get CSRF token
740
+ const getResponse = await request(app).get("/api/csrf-token");
741
+ const csrfToken = getResponse.body.token;
742
+
743
+ const response = await request(app)
744
+ .post("/api/user/profile")
745
+ .set("x-csrf-token", csrfToken)
746
+ .set("Cookie", getResponse.headers["set-cookie"])
747
+ .send({ name: "Test" });
748
+
749
+ expect(response.status).not.toBe(403);
750
+ });
751
+ });
752
+
753
+ // tests/security/auth.test.ts
754
+ import { validatePassword, hashPassword, verifyPassword } from "@/lib/security/password";
755
+
756
+ describe("Authentication Security", () => {
757
+ describe("Password Validation", () => {
758
+ it("rejects short passwords", () => {
759
+ const result = validatePassword("Short1!");
760
+ expect(result.valid).toBe(false);
761
+ expect(result.errors).toContain("Password must be at least 12 characters");
762
+ });
763
+
764
+ it("requires complexity", () => {
765
+ const result = validatePassword("simplelongpassword");
766
+ expect(result.valid).toBe(false);
767
+ expect(result.errors.length).toBeGreaterThan(0);
768
+ });
769
+
770
+ it("accepts strong passwords", () => {
771
+ const result = validatePassword("SecureP@ssw0rd123!");
772
+ expect(result.valid).toBe(true);
773
+ expect(result.errors).toHaveLength(0);
774
+ });
775
+ });
776
+
777
+ describe("Password Hashing", () => {
778
+ it("hashes passwords securely", async () => {
779
+ const password = "SecureP@ssw0rd123!";
780
+ const hash = await hashPassword(password);
781
+
782
+ expect(hash).not.toBe(password);
783
+ expect(hash.startsWith("$2b$")).toBe(true);
784
+ });
785
+
786
+ it("verifies correct passwords", async () => {
787
+ const password = "SecureP@ssw0rd123!";
788
+ const hash = await hashPassword(password);
789
+
790
+ expect(await verifyPassword(password, hash)).toBe(true);
791
+ });
792
+
793
+ it("rejects incorrect passwords", async () => {
794
+ const hash = await hashPassword("SecureP@ssw0rd123!");
795
+
796
+ expect(await verifyPassword("WrongPassword1!", hash)).toBe(false);
797
+ });
798
+ });
799
+ });
800
+ ```
801
+
802
+ ## Use Cases
803
+
804
+ ### Security Audit Checklist
805
+
806
+ ```typescript
807
+ // lib/security/audit.ts
808
+ export interface SecurityAuditResult {
809
+ category: string;
810
+ check: string;
811
+ status: "pass" | "fail" | "warning";
812
+ message: string;
813
+ }
814
+
815
+ export async function runSecurityAudit(): Promise<SecurityAuditResult[]> {
816
+ const results: SecurityAuditResult[] = [];
817
+
818
+ // Check HTTPS
819
+ results.push({
820
+ category: "Transport",
821
+ check: "HTTPS Enabled",
822
+ status: process.env.NODE_ENV === "production" ? "pass" : "warning",
823
+ message: "HTTPS should be enabled in production",
824
+ });
825
+
826
+ // Check security headers
827
+ results.push({
828
+ category: "Headers",
829
+ check: "Security Headers",
830
+ status: "pass",
831
+ message: "Helmet middleware configured",
832
+ });
833
+
834
+ // Check rate limiting
835
+ results.push({
836
+ category: "Rate Limiting",
837
+ check: "Login Rate Limiting",
838
+ status: "pass",
839
+ message: "Login endpoints rate limited",
840
+ });
841
+
842
+ // Check CSRF protection
843
+ results.push({
844
+ category: "CSRF",
845
+ check: "CSRF Protection",
846
+ status: "pass",
847
+ message: "CSRF tokens required for state-changing requests",
848
+ });
849
+
850
+ return results;
851
+ }
852
+ ```
853
+
854
+ ### Dependency Scanning
855
+
856
+ ```yaml
857
+ # .github/workflows/security.yml
858
+ name: Security Scan
859
+
860
+ on:
861
+ push:
862
+ branches: [main]
863
+ schedule:
864
+ - cron: "0 0 * * *" # Daily
865
+
866
+ jobs:
867
+ security:
868
+ runs-on: ubuntu-latest
869
+ steps:
870
+ - uses: actions/checkout@v4
871
+
872
+ - name: Run npm audit
873
+ run: npm audit --audit-level=high
874
+
875
+ - name: Run Snyk scan
876
+ uses: snyk/actions/node@master
877
+ env:
878
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
879
+
880
+ - name: Run SAST scan
881
+ uses: github/codeql-action/analyze@v3
882
+ ```
883
+
884
+ ## Best Practices
885
+
886
+ ### Do's
887
+
888
+ - Validate all user input on the server
889
+ - Use parameterized queries for database access
890
+ - Implement proper authentication and session management
891
+ - Set security headers on all responses
892
+ - Use HTTPS for all communications
893
+ - Implement rate limiting on sensitive endpoints
894
+ - Log security events for monitoring
895
+ - Keep dependencies updated
896
+ - Conduct regular security audits
897
+ - Follow the principle of least privilege
898
+
899
+ ### Don'ts
900
+
901
+ - Don't trust client-side validation alone
902
+ - Don't store sensitive data in plain text
903
+ - Don't expose detailed error messages to users
904
+ - Don't use deprecated cryptographic algorithms
905
+ - Don't disable security features for convenience
906
+ - Don't hardcode secrets in source code
907
+ - Don't ignore security warnings
908
+ - Don't use eval() or similar functions
909
+ - Don't allow unlimited file uploads
910
+ - Don't skip security testing
911
+
912
+ ## References
913
+
914
+ - [OWASP Top 10](https://owasp.org/www-project-top-ten/)
915
+ - [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
916
+ - [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)
917
+ - [OWASP ASVS](https://owasp.org/www-project-application-security-verification-standard/)
918
+ - [CWE Top 25](https://cwe.mitre.org/top25/archive/2023/2023_top25_list.html)