@lhi/tdd-audit 1.1.2 → 1.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/SKILL.md CHANGED
@@ -9,12 +9,18 @@ Applying Test-Driven Development (TDD) to code that has already been generated r
9
9
 
10
10
  ## Autonomous Audit Mode
11
11
  If the user asks you to "Run the TDD Remediation Auto-Audit" or asks you to implement this on your own:
12
- 1. **Explore**: Proactively use `Glob`, `Grep`, and `Read` to scan the repository. Focus on `controllers/`, `routes/`, `api/`, `middleware/`, and database files. Search for anti-patterns: unparameterized SQL queries, missing ownership checks, unsafe HTML rendering, and command injection sinks. Full search patterns are in [auto-audit.md](./prompts/auto-audit.md).
12
+ 1. **Explore**: Proactively use `Glob`, `Grep`, and `Read` to scan the repository. Focus on:
13
+ - **Backend/API**: `controllers/`, `routes/`, `api/`, `handlers/`, `middleware/`, `services/`, `models/`
14
+ - **React / Next.js**: `pages/api/`, `app/api/`, `components/`, `hooks/`, `context/`, `store/`
15
+ - **React Native / Expo**: `screens/`, `navigation/`, `app/`, `app.json`, `app.config.js`
16
+ - **Flutter / Dart**: `lib/screens/`, `lib/services/`, `lib/api/`, `lib/repositories/`, `pubspec.yaml`
17
+ Search for anti-patterns across the full vulnerability surface: SQL/NoSQL/Template injection, IDOR, XSS, command injection, path traversal, SSRF, open redirects, broken auth, mass assignment, prototype pollution, weak crypto, sensitive storage, TLS bypasses, hardcoded secrets, missing rate limiting, missing security headers, CORS wildcards, XXE, insecure deserialization, WebView JS bridge exposure. Full search patterns are in [auto-audit.md](./prompts/auto-audit.md).
13
18
  2. **Plan**: Present a structured list of vulnerabilities (grouped by severity: CRITICAL / HIGH / MEDIUM / LOW) and get confirmation before making any changes.
14
19
  3. **Self-Implement**: For *each* confirmed vulnerability, autonomously execute the complete 3-phase protocol:
15
20
  - **[Phase 1 (Red)](./prompts/red-phase.md)**: Write the exploit test ensuring it fails.
16
21
  - **[Phase 2 (Green)](./prompts/green-phase.md)**: Write the security patch ensuring the test passes.
17
22
  - **[Phase 3 (Refactor)](./prompts/refactor-phase.md)**: Run the full test suite and ensure no business logic broke.
23
+ 4. **[Phase 4 (Hardening)](./prompts/hardening-phase.md)**: After all vulnerabilities are remediated, apply proactive defense-in-depth controls: security headers (Helmet), CSP, CSRF protection, rate limiting audit, dependency vulnerability scan, secret history scan, production error handling, and SRI for third-party scripts.
18
24
  Move methodically through vulnerabilities one by one, CRITICAL-first. Do not advance until the current vulnerability is fully remediated.
19
25
 
20
26
  ---
package/index.js CHANGED
@@ -23,6 +23,9 @@ const targetWorkflowDir = isClaude
23
23
  // ─── 1. Framework Detection ──────────────────────────────────────────────────
24
24
 
25
25
  function detectFramework() {
26
+ // Flutter / Dart — check before package.json since a Flutter project may have both
27
+ if (fs.existsSync(path.join(projectDir, 'pubspec.yaml'))) return 'flutter';
28
+
26
29
  const pkgPath = path.join(projectDir, 'package.json');
27
30
  if (fs.existsSync(pkgPath)) {
28
31
  try {
@@ -43,6 +46,25 @@ function detectFramework() {
43
46
  return 'jest';
44
47
  }
45
48
 
49
+ // Detect the UI framework for richer scan context (React, Next.js, RN, Expo, Flutter)
50
+ function detectAppFramework() {
51
+ if (fs.existsSync(path.join(projectDir, 'pubspec.yaml'))) return 'flutter';
52
+ const pkgPath = path.join(projectDir, 'package.json');
53
+ if (fs.existsSync(pkgPath)) {
54
+ try {
55
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
56
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
57
+ if (deps.expo) return 'expo';
58
+ if (deps['react-native']) return 'react-native';
59
+ if (deps.next) return 'nextjs';
60
+ if (deps.react) return 'react';
61
+ } catch {}
62
+ }
63
+ return null;
64
+ }
65
+
66
+ const appFramework = detectAppFramework();
67
+
46
68
  const framework = detectFramework();
47
69
 
48
70
  // ─── 2. Test Directory Detection ─────────────────────────────────────────────
@@ -71,10 +93,36 @@ const VULN_PATTERNS = [
71
93
  { name: 'XSS', severity: 'HIGH', pattern: /[^/]innerHTML\s*=(?!=)|dangerouslySetInnerHTML\s*=\s*\{\{|document\.write\s*\(|res\.send\s*\(`[^`]*\$\{req\./i },
72
94
  { name: 'Path Traversal', severity: 'HIGH', pattern: /(readFile|sendFile|createReadStream|open)\s*\(.*req\.(params|body|query)|path\.join\s*\([^)]*req\.(params|body|query)/i },
73
95
  { name: 'Broken Auth', severity: 'HIGH', pattern: /jwt\.decode\s*\((?![^;]*\.verify)|verify\s*:\s*false|secret\s*=\s*['"][a-z0-9]{1,20}['"]/i },
96
+ // Vibecoding / mobile stacks
97
+ { name: 'Sensitive Storage', severity: 'HIGH', pattern: /(localStorage|AsyncStorage)\.setItem\s*\(\s*['"](token|password|secret|auth|jwt|api.?key)['"]/i },
98
+ { name: 'TLS Bypass', severity: 'CRITICAL', pattern: /badCertificateCallback[^;]*=\s*true|rejectUnauthorized\s*:\s*false|NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]?0/i },
99
+ { name: 'Hardcoded Secret', severity: 'CRITICAL', skipInTests: true, pattern: /(?:const|final|var|let|static)\s+(?:API_KEY|PRIVATE_KEY|SECRET_KEY|ACCESS_TOKEN|CLIENT_SECRET)\s*=\s*['"][A-Za-z0-9+/=_\-]{20,}['"]/i },
100
+ { name: 'eval() Injection', severity: 'HIGH', pattern: /\beval\s*\([^)]*(?:route\.params|searchParams\.get|req\.(query|body)|params\[)/i },
101
+ // Common vibecoding anti-patterns
102
+ { name: 'Insecure Random', severity: 'HIGH', pattern: /(?:token|sessionId|nonce|secret|csrf)\w*\s*=.*Math\.random\(\)|Math\.random\(\).*(?:token|session|nonce|secret)/i },
103
+ { name: 'Sensitive Log', severity: 'MEDIUM', skipInTests: true, pattern: /console\.(log|info|debug)\([^)]*(?:token|password|secret|jwt|authorization|apiKey|api_key)/i },
104
+ { name: 'Secret Fallback', severity: 'HIGH', pattern: /process\.env\.\w+\s*\|\|\s*['"][A-Za-z0-9+/=_\-]{10,}['"]/i },
105
+ // SSRF, redirects, injection
106
+ { name: 'SSRF', severity: 'CRITICAL', pattern: /\b(?:fetch|axios\.(?:get|post|put|patch|delete|request)|got|https?\.get)\s*\(\s*req\.(?:query|body|params)\./i },
107
+ { name: 'Open Redirect', severity: 'HIGH', pattern: /res\.redirect\s*\(\s*req\.(?:query|body|params)\.|window\.location(?:\.href)?\s*=\s*(?:params\.|route\.params\.|searchParams\.get)/i },
108
+ { name: 'NoSQL Injection', severity: 'HIGH', pattern: /\.(?:find|findOne|findById|updateOne|deleteOne)\s*\(\s*req\.(?:body|query|params)\b|\$where\s*:\s*['"`]/i },
109
+ { name: 'Template Injection', severity: 'HIGH', pattern: /res\.render\s*\(\s*req\.(?:params|body|query)\.|(?:ejs|pug|nunjucks|handlebars)\.render(?:File)?\s*\([^)]*req\.(?:body|params|query)/i },
110
+ { name: 'Insecure Deserialization',severity: 'CRITICAL', pattern: /\.unserialize\s*\(.*req\.|__proto__\s*[=:][^=]|Object\.setPrototypeOf\s*\([^,]+,\s*req\./i },
111
+ // Assignment / pollution
112
+ { name: 'Mass Assignment', severity: 'HIGH', pattern: /new\s+\w+\s*\(\s*req\.body\b|\.create\s*\(\s*req\.body\b|\.update(?:One)?\s*\(\s*\{[^}]*\},\s*req\.body\b/i },
113
+ { name: 'Prototype Pollution', severity: 'HIGH', pattern: /(?:_\.merge|lodash\.merge|deepmerge|hoek\.merge)\s*\([^)]*req\.(?:body|query|params)/i },
114
+ // Crypto / config
115
+ { name: 'Weak Crypto', severity: 'HIGH', pattern: /createHash\s*\(\s*['"](?:md5|sha1)['"]\)|(?:md5|sha1)\s*\(\s*(?:password|passwd|pwd|secret)/i },
116
+ { name: 'CORS Wildcard', severity: 'MEDIUM', pattern: /cors\s*\(\s*\{\s*origin\s*:\s*['"]?\*['"]?|'Access-Control-Allow-Origin',\s*['"]?\*/i },
117
+ { name: 'Cleartext Traffic', severity: 'MEDIUM', skipInTests: true, pattern: /(?:baseURL|apiUrl|API_URL|endpoint|baseUrl)\s*[:=]\s*['"]http:\/\/(?!localhost|127\.0\.0\.1)/i },
118
+ { name: 'XXE', severity: 'HIGH', pattern: /noent\s*:\s*true|expand_entities\s*=\s*True|resolve_entities\s*=\s*True/i },
119
+ // Mobile / WebView
120
+ { name: 'WebView JS Bridge', severity: 'HIGH', pattern: /addJavascriptInterface\s*\(|javaScriptEnabled\s*:\s*true|allowFileAccess\s*:\s*true|allowUniversalAccessFromFileURLs\s*:\s*true/i },
121
+ { name: 'Deep Link Injection', severity: 'MEDIUM', pattern: /Linking\.getInitialURL\s*\(\)|Linking\.addEventListener\s*\(\s*['"]url['"]/i },
74
122
  ];
75
123
 
76
- const SCAN_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.go']);
77
- const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', '__pycache__', 'venv', '.venv', 'vendor']);
124
+ const SCAN_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.go', '.dart']);
125
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', '__pycache__', 'venv', '.venv', 'vendor', '.expo', '.dart_tool', '.pub-cache']);
78
126
 
79
127
  function* walkFiles(dir) {
80
128
  let entries;
@@ -87,9 +135,65 @@ function* walkFiles(dir) {
87
135
  }
88
136
  }
89
137
 
138
+ // Returns true for test/spec files — used to down-weight false-positive-prone patterns
139
+ function isTestFile(filePath) {
140
+ const rel = path.relative(projectDir, filePath).replace(/\\/g, '/');
141
+ return /[._-]test\.[a-z]+$|[._-]spec\.[a-z]+$|_test\.dart$|\/tests?\/|\/spec\/|\/test_/.test(rel);
142
+ }
143
+
144
+ // Scan app.json / app.config.* for embedded secrets (common Expo vibecoding issue)
145
+ function scanAppConfig() {
146
+ const findings = [];
147
+ const configCandidates = ['app.json', 'app.config.js', 'app.config.ts'];
148
+ const secretPattern = /['"]?(?:apiKey|api_key|secret|privateKey|accessToken|clientSecret)['"]?\s*[:=]\s*['"][A-Za-z0-9+/=_\-]{20,}['"]/i;
149
+
150
+ for (const name of configCandidates) {
151
+ const filePath = path.join(projectDir, name);
152
+ if (!fs.existsSync(filePath)) continue;
153
+ let lines;
154
+ try { lines = fs.readFileSync(filePath, 'utf8').split('\n'); } catch { continue; }
155
+ for (let i = 0; i < lines.length; i++) {
156
+ if (secretPattern.test(lines[i])) {
157
+ findings.push({
158
+ severity: 'CRITICAL',
159
+ name: 'Config Secret',
160
+ file: name,
161
+ line: i + 1,
162
+ snippet: lines[i].trim().slice(0, 80),
163
+ inTestFile: false,
164
+ });
165
+ }
166
+ }
167
+ }
168
+ return findings;
169
+ }
170
+
171
+ function scanAndroidManifest() {
172
+ const findings = [];
173
+ const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
174
+ if (!fs.existsSync(manifestPath)) return findings;
175
+ let lines;
176
+ try { lines = fs.readFileSync(manifestPath, 'utf8').split('\n'); } catch { return findings; }
177
+ for (let i = 0; i < lines.length; i++) {
178
+ if (/android:debuggable\s*=\s*["']true["']/i.test(lines[i])) {
179
+ findings.push({
180
+ severity: 'HIGH',
181
+ name: 'Android Debuggable',
182
+ file: 'android/app/src/main/AndroidManifest.xml',
183
+ line: i + 1,
184
+ snippet: lines[i].trim().slice(0, 80),
185
+ inTestFile: false,
186
+ likelyFalsePositive: false,
187
+ });
188
+ }
189
+ }
190
+ return findings;
191
+ }
192
+
90
193
  function quickScan() {
91
194
  const findings = [];
92
195
  for (const filePath of walkFiles(projectDir)) {
196
+ const inTest = isTestFile(filePath);
93
197
  let lines;
94
198
  try { lines = fs.readFileSync(filePath, 'utf8').split('\n'); } catch { continue; }
95
199
  for (let i = 0; i < lines.length; i++) {
@@ -101,13 +205,15 @@ function quickScan() {
101
205
  file: path.relative(projectDir, filePath),
102
206
  line: i + 1,
103
207
  snippet: lines[i].trim().slice(0, 80),
208
+ inTestFile: inTest,
209
+ likelyFalsePositive: inTest && !!vuln.skipInTests,
104
210
  });
105
211
  break; // one finding per line
106
212
  }
107
213
  }
108
214
  }
109
215
  }
110
- return findings;
216
+ return [...findings, ...scanAppConfig(), ...scanAndroidManifest()];
111
217
  }
112
218
 
113
219
  function printFindings(findings) {
@@ -115,18 +221,30 @@ function printFindings(findings) {
115
221
  console.log(' ✅ No obvious vulnerability patterns detected.\n');
116
222
  return;
117
223
  }
224
+ const real = findings.filter(f => !f.likelyFalsePositive);
225
+ const noisy = findings.filter(f => f.likelyFalsePositive);
226
+
118
227
  const bySeverity = { CRITICAL: [], HIGH: [], MEDIUM: [], LOW: [] };
119
- for (const f of findings) (bySeverity[f.severity] || bySeverity.LOW).push(f);
228
+ for (const f of real) (bySeverity[f.severity] || bySeverity.LOW).push(f);
120
229
  const icons = { CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🔵' };
121
230
 
122
- console.log(`\n Found ${findings.length} potential issue(s):\n`);
231
+ console.log(`\n Found ${real.length} potential issue(s)${noisy.length ? ` (+${noisy.length} in test files — see below)` : ''}:\n`);
123
232
  for (const [sev, list] of Object.entries(bySeverity)) {
124
233
  if (!list.length) continue;
125
234
  for (const f of list) {
126
- console.log(` ${icons[sev]} [${sev}] ${f.name} ${f.file}:${f.line}`);
235
+ const testBadge = f.inTestFile ? ' [test file]' : '';
236
+ console.log(` ${icons[sev]} [${sev}] ${f.name} — ${f.file}:${f.line}${testBadge}`);
127
237
  console.log(` ${f.snippet}`);
128
238
  }
129
239
  }
240
+
241
+ if (noisy.length) {
242
+ console.log('\n ⚪ Likely intentional (in test files — verify manually):');
243
+ for (const f of noisy) {
244
+ console.log(` ${f.name} — ${f.file}:${f.line}`);
245
+ }
246
+ }
247
+
130
248
  console.log('\n Run /tdd-audit in your agent to remediate.\n');
131
249
  }
132
250
 
@@ -142,7 +260,8 @@ if (scanOnly) {
142
260
 
143
261
  // ─── 5. Install Skill Files ───────────────────────────────────────────────────
144
262
 
145
- console.log(`\nInstalling TDD Remediation Skill (${isLocal ? 'local' : 'global'}, framework: ${framework}, test dir: ${testBaseDir}/)...\n`);
263
+ const appLabel = appFramework ? `, app: ${appFramework}` : '';
264
+ console.log(`\nInstalling TDD Remediation Skill (${isLocal ? 'local' : 'global'}, framework: ${framework}${appLabel}, test dir: ${testBaseDir}/)...\n`);
146
265
 
147
266
  if (!fs.existsSync(targetSkillDir)) fs.mkdirSync(targetSkillDir, { recursive: true });
148
267
 
@@ -160,11 +279,12 @@ if (!fs.existsSync(targetTestDir)) {
160
279
  }
161
280
 
162
281
  const testTemplateMap = {
163
- jest: 'sample.exploit.test.js',
164
- vitest: 'sample.exploit.test.vitest.js',
165
- mocha: 'sample.exploit.test.js',
166
- pytest: 'sample.exploit.test.pytest.py',
167
- go: 'sample.exploit.test.go',
282
+ jest: 'sample.exploit.test.js',
283
+ vitest: 'sample.exploit.test.vitest.js',
284
+ mocha: 'sample.exploit.test.js',
285
+ pytest: 'sample.exploit.test.pytest.py',
286
+ go: 'sample.exploit.test.go',
287
+ flutter: 'sample.exploit.test.dart',
168
288
  };
169
289
 
170
290
  const testTemplateName = testTemplateMap[framework];
@@ -217,11 +337,12 @@ const ciWorkflowPath = path.join(ciWorkflowDir, 'security-tests.yml');
217
337
 
218
338
  if (!fs.existsSync(ciWorkflowPath)) {
219
339
  const ciTemplateMap = {
220
- jest: 'security-tests.node.yml',
221
- vitest: 'security-tests.node.yml',
222
- mocha: 'security-tests.node.yml',
223
- pytest: 'security-tests.python.yml',
224
- go: 'security-tests.go.yml',
340
+ jest: 'security-tests.node.yml',
341
+ vitest: 'security-tests.node.yml',
342
+ mocha: 'security-tests.node.yml',
343
+ pytest: 'security-tests.python.yml',
344
+ go: 'security-tests.go.yml',
345
+ flutter: 'security-tests.flutter.yml',
225
346
  };
226
347
  const ciTemplatePath = path.join(__dirname, 'templates', 'workflows', ciTemplateMap[framework]);
227
348
  if (fs.existsSync(ciTemplatePath)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhi/tdd-audit",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "description": "Anti-Gravity Skill for TDD Remediation. Patches security vulnerabilities using a Red-Green-Refactor protocol with automated exploit tests.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -31,7 +31,14 @@
31
31
  "audit",
32
32
  "claude",
33
33
  "ai-agent",
34
- "skill"
34
+ "skill",
35
+ "react",
36
+ "nextjs",
37
+ "react-native",
38
+ "expo",
39
+ "flutter",
40
+ "mobile-security",
41
+ "vibecoding"
35
42
  ],
36
43
  "engines": {
37
44
  "node": ">=16.7.0"
@@ -6,11 +6,29 @@ When invoked in Auto-Audit mode, proactively secure the user's entire repository
6
6
 
7
7
  ### 0a. Explore the Architecture
8
8
  Use `Glob` and `Read` to understand the project structure. Focus on:
9
+
10
+ **Backend / API**
9
11
  - `controllers/`, `routes/`, `api/`, `handlers/` — request entry points
10
12
  - `services/`, `models/`, `db/`, `repositories/` — data access
11
13
  - `middleware/`, `utils/`, `helpers/`, `lib/` — shared utilities
12
14
  - Config files: `*.env`, `config.js`, `settings.py` — secrets and security settings
13
15
 
16
+ **React / Next.js**
17
+ - `pages/api/`, `app/api/` — Next.js API routes (check for missing auth)
18
+ - `components/`, `app/`, `pages/` — UI components (check for `dangerouslySetInnerHTML`, `eval`)
19
+ - `hooks/`, `context/`, `store/` — state management (check for sensitive data leakage)
20
+
21
+ **React Native / Expo**
22
+ - `screens/`, `navigation/`, `app/` — screen components (check `route.params` usage)
23
+ - `services/`, `api/`, `utils/` — API calls (check TLS config, token storage)
24
+ - `app.json`, `app.config.js` — Expo config (check for embedded keys)
25
+
26
+ **Flutter / Dart**
27
+ - `lib/screens/`, `lib/pages/`, `lib/views/` — UI layer
28
+ - `lib/services/`, `lib/api/`, `lib/repositories/` — data layer (check HTTP client config)
29
+ - `lib/utils/`, `lib/helpers/` — shared utilities
30
+ - `pubspec.yaml` — dependency audit
31
+
14
32
  ### 0b. Search for Anti-Patterns
15
33
  Use `Grep` with the following patterns to surface candidates. Read the matched files to confirm before reporting.
16
34
 
@@ -71,6 +89,131 @@ router\.(post|put|delete) # mutation routes (check for rate-limit middleware)
71
89
  app\.post\( # POST handlers (check for rate-limit middleware)
72
90
  ```
73
91
 
92
+ **Sensitive Storage (React / React Native / Expo)**
93
+ ```
94
+ AsyncStorage\.setItem.*token # token stored in unencrypted AsyncStorage
95
+ localStorage\.setItem.*token # token stored in localStorage (XSS-accessible)
96
+ AsyncStorage\.setItem.*password # password stored in plain AsyncStorage
97
+ SecureStore vs AsyncStorage # confirm sensitive values use expo-secure-store
98
+ ```
99
+
100
+ **TLS / Certificate Bypass**
101
+ ```
102
+ rejectUnauthorized.*false # Node.js TLS verification disabled
103
+ badCertificateCallback.*true # Dart/Flutter TLS bypass
104
+ NODE_TLS_REJECT_UNAUTHORIZED=0 # env-level TLS disable
105
+ ```
106
+
107
+ **Hardcoded Secrets (vibecoded apps)**
108
+ ```
109
+ API_KEY\s*=\s*['"][A-Za-z0-9]{20,} # hardcoded API key in source
110
+ PRIVATE_KEY\s*=\s*['"] # private key in source
111
+ SECRET_KEY\s*=\s*['"] # secret embedded in code
112
+ process\.env\.\w+\s*\|\|\s*['"] # env var with hardcoded fallback
113
+ ```
114
+
115
+ **Next.js API Route Auth**
116
+ ```
117
+ export.*async.*handler # Next.js API route — check for missing auth guard
118
+ export default.*req.*res # pages/api handler — verify authentication
119
+ getServerSideProps.*params # SSR with params — check for injection
120
+ ```
121
+
122
+ **React Native / Expo Navigation Injection**
123
+ ```
124
+ route\.params\.\w+.*query # route param passed to DB/API query
125
+ route\.params\.\w+.*fetch # route param used in fetch URL
126
+ navigation\.navigate.*params # user-controlled navigation params
127
+ ```
128
+
129
+ **Flutter / Dart**
130
+ ```
131
+ http\.get\( # raw http call — check for TLS config
132
+ http\.post\( # raw http call — check for TLS config
133
+ SharedPreferences.*setString.*token # token in unencrypted SharedPreferences
134
+ Platform\.environment\[ # env access in Flutter — check for secrets
135
+ ```
136
+
137
+ **SSRF (Server-Side Request Forgery)**
138
+ ```
139
+ fetch\(.*req\.(query|body|params) # fetch with user-controlled URL
140
+ axios\.(get|post)\(.*req\.body # axios with user-controlled target
141
+ got\(.*req\.(query|params) # got with user-controlled URL
142
+ ```
143
+
144
+ **Open Redirect**
145
+ ```
146
+ res\.redirect\(.*req\.(query|body) # redirecting to user-supplied URL
147
+ window\.location.*=.*params\. # client-side redirect from route params
148
+ router\.push\(.*searchParams # Next.js/RN push with user param
149
+ ```
150
+
151
+ **NoSQL Injection**
152
+ ```
153
+ \.find\(\s*req\.(body|query) # MongoDB find with raw request object
154
+ \.findOne\(\s*req\.(body|query) # MongoDB findOne with raw request object
155
+ \$where.*: # $where operator (executes JS in Mongo)
156
+ ```
157
+
158
+ **Mass Assignment**
159
+ ```
160
+ new.*Model\(.*req\.body # passing full req.body to constructor
161
+ \.create\(.*req\.body # ORM create with unsanitized body
162
+ \.update.*req\.body # ORM update with unsanitized body
163
+ ```
164
+
165
+ **Prototype Pollution**
166
+ ```
167
+ _\.merge\(.*req\.(body|query) # lodash merge with user input
168
+ deepmerge\(.*req\.(body|query) # deepmerge with user input
169
+ Object\.assign\(\{\}.*req\.body # Object.assign from user input
170
+ ```
171
+
172
+ **Weak Cryptography**
173
+ ```
174
+ createHash\(['"]md5['"] # MD5 for anything security-related
175
+ createHash\(['"]sha1['"] # SHA1 for anything security-related
176
+ md5\(.*password # MD5-hashed password
177
+ sha1\(.*password # SHA1-hashed password
178
+ ```
179
+
180
+ **Missing Security Headers / Rate Limiting**
181
+ ```
182
+ app\.(use|listen) # check: is helmet() present before routes?
183
+ router\.(post|put|delete) # mutation routes — check for rateLimit middleware
184
+ app\.post\('/login # login route — must have rate limit
185
+ app\.post\('/register # register route — must have rate limit
186
+ ```
187
+
188
+ **CORS Misconfiguration**
189
+ ```
190
+ cors\(\{.*origin.*\* # wildcard CORS origin
191
+ Access-Control-Allow-Origin.*\* # wildcard CORS header
192
+ ```
193
+
194
+ **Template Injection**
195
+ ```
196
+ res\.render\(.*req\.(params|query) # user-controlled template name
197
+ ejs\.render\(.*req\.body # ejs render with user input
198
+ pug\.render\(.*req\.body # pug render with user input
199
+ ```
200
+
201
+ **Cleartext Traffic / XXE**
202
+ ```
203
+ baseURL.*=.*['"]http://(?!localhost) # non-HTTPS API base URL
204
+ noent.*:.*true # XML entity expansion enabled
205
+ resolve_entities.*True # Python lxml entity expansion
206
+ ```
207
+
208
+ **Dependency Audit**
209
+ ```
210
+ # Run manually — not grep-based:
211
+ # npm audit --audit-level=high
212
+ # pip-audit
213
+ # govulncheck ./...
214
+ # bundle audit
215
+ ```
216
+
74
217
  ### 0c. Present Findings
75
218
  Before touching any code, output a structured **Audit Report** with this format:
76
219