omgkit 2.2.0 → 2.3.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/package.json +1 -1
- package/plugin/skills/databases/mongodb/SKILL.md +60 -776
- package/plugin/skills/databases/prisma/SKILL.md +53 -744
- package/plugin/skills/databases/redis/SKILL.md +53 -860
- package/plugin/skills/devops/aws/SKILL.md +68 -672
- package/plugin/skills/devops/github-actions/SKILL.md +54 -657
- package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +87 -853
- package/plugin/skills/frameworks/express/SKILL.md +95 -1301
- package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
- package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
- package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
- package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
- package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
- package/plugin/skills/frontend/responsive/SKILL.md +76 -799
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
- package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
- package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
- package/plugin/skills/languages/javascript/SKILL.md +106 -849
- package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
- package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
- package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
- package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
- package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
- package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
- package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
- package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
- package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
- package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
- package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
- package/plugin/skills/security/better-auth/SKILL.md +46 -1034
- package/plugin/skills/security/oauth/SKILL.md +80 -934
- package/plugin/skills/security/owasp/SKILL.md +78 -862
- package/plugin/skills/testing/playwright/SKILL.md +77 -700
- package/plugin/skills/testing/pytest/SKILL.md +73 -811
- package/plugin/skills/testing/vitest/SKILL.md +60 -920
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
- package/plugin/skills/SKILL_STANDARDS.md +0 -743
|
@@ -1,913 +1,130 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: OWASP security best practices
|
|
4
|
-
category: security
|
|
5
|
-
triggers:
|
|
6
|
-
- owasp
|
|
7
|
-
- web security
|
|
8
|
-
- security best practices
|
|
9
|
-
- vulnerability prevention
|
|
10
|
-
- secure coding
|
|
11
|
-
- penetration testing
|
|
2
|
+
name: Applying OWASP Security
|
|
3
|
+
description: Claude applies OWASP security best practices to web applications. Use when preventing vulnerabilities, implementing input validation, securing authentication, configuring security headers, or conducting security reviews.
|
|
12
4
|
---
|
|
13
5
|
|
|
14
|
-
# OWASP
|
|
6
|
+
# Applying OWASP Security
|
|
15
7
|
|
|
16
|
-
|
|
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
|
|
8
|
+
## Quick Start
|
|
33
9
|
|
|
34
10
|
```typescript
|
|
35
|
-
// lib/security/
|
|
36
|
-
import {
|
|
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
|
-
}
|
|
112
|
-
|
|
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
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 2. XSS Prevention
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// lib/security/xss.ts
|
|
11
|
+
// lib/security/validation.ts
|
|
12
|
+
import { z } from "zod";
|
|
133
13
|
import DOMPurify from "isomorphic-dompurify";
|
|
134
|
-
import { escape } from "html-escaper";
|
|
135
14
|
|
|
136
|
-
//
|
|
137
|
-
export
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
}
|
|
15
|
+
// Input validation
|
|
16
|
+
export const userSchema = z.object({
|
|
17
|
+
email: z.string().email().max(254),
|
|
18
|
+
password: z.string().min(12).max(128),
|
|
19
|
+
name: z.string().min(2).max(100).regex(/^[\p{L}\s'-]+$/u),
|
|
20
|
+
});
|
|
178
21
|
|
|
179
|
-
//
|
|
180
|
-
|
|
22
|
+
// HTML sanitization
|
|
23
|
+
export const sanitizeHtml = (dirty: string) =>
|
|
24
|
+
DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p"] });
|
|
25
|
+
```
|
|
181
26
|
|
|
182
|
-
|
|
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
|
-
}
|
|
27
|
+
## Features
|
|
191
28
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
29
|
+
| Feature | Description | Reference |
|
|
30
|
+
|---------|-------------|-----------|
|
|
31
|
+
| Injection Prevention | SQL, NoSQL, command injection protection | [OWASP Injection](https://owasp.org/Top10/A03_2021-Injection/) |
|
|
32
|
+
| XSS Prevention | Output encoding and HTML sanitization | [OWASP XSS](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) |
|
|
33
|
+
| CSRF Protection | Token-based cross-site request forgery defense | [OWASP CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) |
|
|
34
|
+
| Authentication Security | Password hashing, rate limiting, session management | [OWASP Auth](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) |
|
|
35
|
+
| Security Headers | CSP, HSTS, X-Frame-Options configuration | [OWASP Headers](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html) |
|
|
36
|
+
| Input Validation | Schema validation and sanitization | [OWASP Validation](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) |
|
|
196
37
|
|
|
197
|
-
|
|
198
|
-
}
|
|
38
|
+
## Common Patterns
|
|
199
39
|
|
|
200
|
-
|
|
201
|
-
const sanitized: Record<string, unknown> = {};
|
|
40
|
+
### Parameterized Queries (SQL Injection Prevention)
|
|
202
41
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
}
|
|
42
|
+
```typescript
|
|
43
|
+
// BAD - SQL injection vulnerable
|
|
44
|
+
const result = await db.$queryRawUnsafe(`SELECT * FROM users WHERE id = '${userId}'`);
|
|
212
45
|
|
|
213
|
-
|
|
214
|
-
}
|
|
46
|
+
// GOOD - Parameterized query
|
|
47
|
+
const result = await db.user.findUnique({ where: { id: userId } });
|
|
48
|
+
const result = await db.$queryRaw`SELECT * FROM users WHERE id = ${userId}`;
|
|
215
49
|
```
|
|
216
50
|
|
|
217
|
-
###
|
|
51
|
+
### CSRF Protection Middleware
|
|
218
52
|
|
|
219
53
|
```typescript
|
|
220
|
-
// lib/security/csrf.ts
|
|
221
54
|
import crypto from "crypto";
|
|
222
|
-
import { Request, Response, NextFunction } from "express";
|
|
223
|
-
|
|
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
55
|
|
|
241
|
-
|
|
242
|
-
|
|
56
|
+
export function csrfProtection(req: Request, res: Response, next: NextFunction) {
|
|
57
|
+
if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next();
|
|
243
58
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
error: "CSRF validation failed",
|
|
247
|
-
message: "Invalid or missing CSRF token",
|
|
248
|
-
});
|
|
249
|
-
}
|
|
59
|
+
const cookieToken = req.cookies["csrf_token"];
|
|
60
|
+
const headerToken = req.headers["x-csrf-token"];
|
|
250
61
|
|
|
251
|
-
|
|
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
|
-
});
|
|
62
|
+
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
|
|
63
|
+
return res.status(403).json({ error: "CSRF validation failed" });
|
|
265
64
|
}
|
|
266
65
|
next();
|
|
267
66
|
}
|
|
67
|
+
```
|
|
268
68
|
|
|
269
|
-
|
|
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();
|
|
69
|
+
### Security Headers Configuration
|
|
278
70
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
headers: {
|
|
282
|
-
...options.headers,
|
|
283
|
-
[CSRF_HEADER]: token || "",
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
};
|
|
71
|
+
```typescript
|
|
72
|
+
import helmet from "helmet";
|
|
287
73
|
|
|
288
|
-
|
|
289
|
-
|
|
74
|
+
app.use(helmet({
|
|
75
|
+
contentSecurityPolicy: {
|
|
76
|
+
directives: {
|
|
77
|
+
defaultSrc: ["'self'"],
|
|
78
|
+
scriptSrc: ["'self'", "'strict-dynamic'"],
|
|
79
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
80
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
81
|
+
frameSrc: ["'none'"],
|
|
82
|
+
objectSrc: ["'none'"],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: true },
|
|
86
|
+
frameguard: { action: "deny" },
|
|
87
|
+
}));
|
|
290
88
|
```
|
|
291
89
|
|
|
292
|
-
###
|
|
90
|
+
### Password Security
|
|
293
91
|
|
|
294
92
|
```typescript
|
|
295
|
-
// lib/security/password.ts
|
|
296
93
|
import bcrypt from "bcrypt";
|
|
297
|
-
import crypto from "crypto";
|
|
298
94
|
|
|
299
95
|
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
96
|
|
|
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
97
|
export async function hashPassword(password: string): Promise<string> {
|
|
346
98
|
return bcrypt.hash(password, SALT_ROUNDS);
|
|
347
99
|
}
|
|
348
100
|
|
|
349
|
-
|
|
350
|
-
export async function verifyPassword(
|
|
351
|
-
password: string,
|
|
352
|
-
hash: string
|
|
353
|
-
): Promise<boolean> {
|
|
101
|
+
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
354
102
|
return bcrypt.compare(password, hash);
|
|
355
103
|
}
|
|
356
104
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
### 5. Security Headers
|
|
427
|
-
|
|
428
|
-
```typescript
|
|
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
|
-
};
|
|
555
|
-
```
|
|
556
|
-
|
|
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("<script>alert("xss")</script>");
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
it("escapes ampersands", () => {
|
|
699
|
-
expect(escapeHtml("&")).toBe("&");
|
|
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;
|
|
105
|
+
export function validatePasswordStrength(password: string): string[] {
|
|
106
|
+
const errors: string[] = [];
|
|
107
|
+
if (password.length < 12) errors.push("Must be at least 12 characters");
|
|
108
|
+
if (!/[a-z]/.test(password)) errors.push("Must contain lowercase");
|
|
109
|
+
if (!/[A-Z]/.test(password)) errors.push("Must contain uppercase");
|
|
110
|
+
if (!/\d/.test(password)) errors.push("Must contain digit");
|
|
111
|
+
if (!/[!@#$%^&*]/.test(password)) errors.push("Must contain special character");
|
|
112
|
+
return errors;
|
|
851
113
|
}
|
|
852
114
|
```
|
|
853
115
|
|
|
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
116
|
## Best Practices
|
|
885
117
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
|
118
|
+
| Do | Avoid |
|
|
119
|
+
|----|-------|
|
|
120
|
+
| Validate all input on the server side | Trusting client-side validation alone |
|
|
121
|
+
| Use parameterized queries for all DB access | String concatenation in queries |
|
|
122
|
+
| Set security headers on all responses | Disabling security features for convenience |
|
|
123
|
+
| Implement rate limiting on sensitive endpoints | Allowing unlimited attempts |
|
|
124
|
+
| Hash passwords with bcrypt (12+ rounds) | Using weak/deprecated crypto algorithms |
|
|
125
|
+
| Log security events for monitoring | Exposing detailed error messages to users |
|
|
126
|
+
| Keep dependencies updated | Ignoring security warnings |
|
|
127
|
+
| Use HTTPS for all communications | Hardcoding secrets in source code |
|
|
911
128
|
|
|
912
129
|
## References
|
|
913
130
|
|
|
@@ -915,4 +132,3 @@ jobs:
|
|
|
915
132
|
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
|
|
916
133
|
- [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)
|
|
917
134
|
- [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)
|