@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 +7 -1
- package/index.js +138 -17
- package/package.json +9 -2
- package/prompts/auto-audit.md +143 -0
- package/prompts/green-phase.md +435 -0
- package/prompts/hardening-phase.md +243 -0
- package/prompts/red-phase.md +154 -0
- package/prompts/refactor-phase.md +26 -0
- package/templates/sample.exploit.test.dart +52 -0
- package/templates/sample.exploit.test.react.tsx +59 -0
- package/templates/workflows/security-tests.flutter.yml +26 -0
- package/workflows/tdd-audit.md +8 -1
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
|
|
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
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
164
|
-
vitest:
|
|
165
|
-
mocha:
|
|
166
|
-
pytest:
|
|
167
|
-
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:
|
|
221
|
-
vitest:
|
|
222
|
-
mocha:
|
|
223
|
-
pytest:
|
|
224
|
-
go:
|
|
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.
|
|
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"
|
package/prompts/auto-audit.md
CHANGED
|
@@ -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
|
|