@lhi/tdd-audit 1.11.0 → 1.12.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/README.md +6 -5
- package/docs/vulnerability-patterns.md +137 -1
- package/lib/scanner.js +95 -2
- package/package.json +9 -1
- package/prompts/auto-audit.md +76 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lhi/tdd-audit
|
|
2
2
|
|
|
3
|
-
> **v1.
|
|
3
|
+
> **v1.12.0** — Security skill installer for **Claude Code, Gemini CLI, Cursor, Codex, and OpenCode**. Patches vulnerabilities using a Red-Green-Refactor exploit-test protocol — prove the hole exists, apply the fix, prove it's closed.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ npx @lhi/tdd-audit
|
|
|
10
10
|
|
|
11
11
|
On first run the installer:
|
|
12
12
|
|
|
13
|
-
1. Scans your codebase for **
|
|
13
|
+
1. Scans your codebase for **57 vulnerability patterns** across 6 scanner modules and prints a severity-ranked report
|
|
14
14
|
2. Scaffolds `__tests__/security/` with a framework-matched exploit test boilerplate
|
|
15
15
|
3. Adds `test:security` to `package.json`
|
|
16
16
|
4. Creates `.github/workflows/security-tests.yml` with SHA-pinned actions and `npm audit`
|
|
@@ -108,15 +108,16 @@ npx @lhi/tdd-audit --scan # human-readable text (default)
|
|
|
108
108
|
|
|
109
109
|
## Testing
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
463 tests across unit, E2E, and security suites:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
114
|
npm test # full suite
|
|
115
|
-
npm run test:unit # unit tests with coverage
|
|
115
|
+
npm run test:unit # unit tests with coverage (91.6% branch coverage)
|
|
116
116
|
npm run test:security # security regression tests only
|
|
117
|
+
npm run test:e2e # end-to-end REST API tests
|
|
117
118
|
```
|
|
118
119
|
|
|
119
|
-
Security tests cover prompt injection, path traversal, rate limiting, timing-safe auth, job store bounds, SARIF schema, and more. See [
|
|
120
|
+
Security tests cover prompt injection, path traversal, rate limiting, timing-safe auth, job store bounds, SARIF schema, and more. See [__tests__/security/](__tests__/security/) for all 22 regression tests.
|
|
120
121
|
|
|
121
122
|
## Documentation
|
|
122
123
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vulnerability Patterns Reference
|
|
2
2
|
|
|
3
|
-
All
|
|
3
|
+
All 57 patterns detected by `@lhi/tdd-audit` across 6 scanner modules. Source patterns are checked against `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.py`, `.go`, `.dart`, `.yml`, and `.yaml` files line-by-line. Prompt/skill patterns are checked separately against `.md` files in agent configuration directories. Supply-chain patterns check `package.json`. NEXT_PUBLIC secret patterns also check `.env*` files.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -198,3 +198,139 @@ These patterns are checked against `.md` files in `prompts/`, `skills/`, `.claud
|
|
|
198
198
|
**Grep signature:** `android:debuggable="true"`
|
|
199
199
|
**Why it matters:** Debug builds expose the app to `adb` inspection and arbitrary code injection on the device.
|
|
200
200
|
**Fix:** Remove `android:debuggable` from `AndroidManifest.xml` (the build system sets it correctly per variant).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## AI / LLM Security (CRITICAL)
|
|
205
|
+
|
|
206
|
+
### LLM Prompt Injection (CRITICAL)
|
|
207
|
+
**Grep signature:** `{ role: "user", content: req.body... }`, `messages.push(req.body|query|params)`
|
|
208
|
+
**Why it matters:** Untrusted user input injected directly into LLM messages enables attackers to hijack model behavior, exfiltrate data, or bypass safety controls.
|
|
209
|
+
**Fix:** Sanitize and validate user input before insertion. Use a system-prompt separation layer. Never concatenate raw request data into the messages array.
|
|
210
|
+
|
|
211
|
+
### LLM Output Execution (CRITICAL)
|
|
212
|
+
**Grep signature:** `eval(response)`, `eval(result)`, `eval(output)`, `eval(completion)`
|
|
213
|
+
**Why it matters:** Executing model-generated code gives an attacker who controls the model's context (or poisons its training) arbitrary code execution on your server.
|
|
214
|
+
**Fix:** Never `eval()` LLM output. Use a sandboxed interpreter (Pyodide, isolated subprocess) or structured output schemas.
|
|
215
|
+
|
|
216
|
+
### LangChain ShellTool (CRITICAL)
|
|
217
|
+
**Grep signature:** `ShellTool()`, `LLMMathChain.from_llm(`, `PALChain.from_llm(`
|
|
218
|
+
**Why it matters:** These tools execute shell commands or `eval()` LLM-generated Python on the host system. A malicious prompt achieves full RCE (CVE-2023-29374, CVSS 9.8).
|
|
219
|
+
**Fix:** Remove `ShellTool` from agent toolkits. Replace `LLMMathChain`/`PALChain` with `numexpr` or sandboxed math evaluators.
|
|
220
|
+
|
|
221
|
+
### Dynamic Require (CRITICAL)
|
|
222
|
+
**Grep signature:** `require(req.query.`, `require(req.body.`, `require(req.params.`
|
|
223
|
+
**Why it matters:** Attacker controls which Node.js module is loaded — can load `child_process`, `fs`, or custom malicious modules.
|
|
224
|
+
**Fix:** Use a static map of allowed modules. Never pass user input to `require()`.
|
|
225
|
+
|
|
226
|
+
### VM Code Injection (CRITICAL)
|
|
227
|
+
**Grep signature:** `vm.runInNewContext(req.body`, `vm.runInContext(req.query`
|
|
228
|
+
**Why it matters:** Node.js's `vm` module is not a security boundary — sandbox escape is possible with crafted prototypes. Attacker achieves full RCE.
|
|
229
|
+
**Fix:** Use a proper sandbox (isolated-vm, Deno subprocess) for user-supplied code execution.
|
|
230
|
+
|
|
231
|
+
### node-serialize RCE (CRITICAL)
|
|
232
|
+
**Grep signature:** `require('node-serialize')`
|
|
233
|
+
**Why it matters:** `node-serialize` is known to be vulnerable to remote code execution via IIFE injection in serialized strings. Any use is an immediate CRITICAL risk.
|
|
234
|
+
**Fix:** Uninstall `node-serialize`. Use `JSON.parse()` / `JSON.stringify()` with schema validation.
|
|
235
|
+
|
|
236
|
+
### Hardcoded OpenAI Key (CRITICAL)
|
|
237
|
+
**Grep signature:** `'sk-proj-...'`, `'sk-...T3BlbkFJ...'` (≥60 chars)
|
|
238
|
+
**Note:** `skipInTests: true`
|
|
239
|
+
**Why it matters:** A committed API key gives anyone with repo access unlimited access to your OpenAI account and budget.
|
|
240
|
+
**Fix:** Use `process.env.OPENAI_API_KEY`. Rotate the key immediately. Run `gitleaks` on git history.
|
|
241
|
+
|
|
242
|
+
### Hardcoded Anthropic Key (CRITICAL)
|
|
243
|
+
**Grep signature:** `'sk-ant-api03-...'`
|
|
244
|
+
**Note:** `skipInTests: true`
|
|
245
|
+
**Why it matters:** Committed Anthropic API key leaks billing access and all Claude API capabilities.
|
|
246
|
+
**Fix:** Use environment variables. Rotate immediately via the Anthropic console.
|
|
247
|
+
|
|
248
|
+
### GitHub Actions Injection (CRITICAL)
|
|
249
|
+
**Files checked:** `.yml` and `.yaml` workflow files
|
|
250
|
+
**Grep signature:** `${{ github.event.pull_request.title }}`, `${{ github.head_ref }}`, `${{ github.event.issue.body }}`
|
|
251
|
+
**Why it matters:** These GitHub context values are attacker-controlled (PR title, branch name, issue body). Interpolating them into a `run:` step enables arbitrary command execution in your CI pipeline.
|
|
252
|
+
**Fix:** Use an intermediate environment variable: `env: TITLE: ${{ github.event.pull_request.title }}` then reference `$TITLE` in the shell script.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Electron / Desktop Security
|
|
257
|
+
|
|
258
|
+
### Electron nodeIntegration (CRITICAL)
|
|
259
|
+
**Grep signature:** `nodeIntegration: true`
|
|
260
|
+
**Why it matters:** Enables Node.js APIs in the renderer process. Any XSS in a web page loaded by the app achieves full system compromise.
|
|
261
|
+
**Fix:** Set `nodeIntegration: false` (default). Use `contextBridge` to expose specific APIs.
|
|
262
|
+
|
|
263
|
+
### Electron webSecurity Off (CRITICAL)
|
|
264
|
+
**Grep signature:** `webSecurity: false`
|
|
265
|
+
**Why it matters:** Disables the same-origin policy in the renderer, allowing cross-origin reads and mixed-content loads.
|
|
266
|
+
**Fix:** Never disable `webSecurity`. Fix CORS configuration on the server instead.
|
|
267
|
+
|
|
268
|
+
### Electron contextIsolation Off (HIGH)
|
|
269
|
+
**Grep signature:** `contextIsolation: false`
|
|
270
|
+
**Why it matters:** When context isolation is off, the renderer's JavaScript shares a prototype chain with the preload script, enabling prototype pollution attacks from web content.
|
|
271
|
+
**Fix:** Set `contextIsolation: true` (default since Electron 12). Use `contextBridge.exposeInMainWorld` for IPC.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## AI / LLM Security (HIGH)
|
|
276
|
+
|
|
277
|
+
### Header Injection (HIGH)
|
|
278
|
+
**Grep signature:** `res.setHeader(x, req.body|query|params)`, `res.set(x, req.body|query|params)`
|
|
279
|
+
**Why it matters:** Attacker can inject HTTP response headers, enabling cache poisoning, CORS bypass, or CSP override.
|
|
280
|
+
**Fix:** Validate and allowlist header values before passing to `res.setHeader()`.
|
|
281
|
+
|
|
282
|
+
### XPath Injection (HIGH)
|
|
283
|
+
**Grep signature:** `xpath.select(req.query|body|params`, `xpath.evaluate(req.`
|
|
284
|
+
**Why it matters:** Attacker can manipulate XPath queries to bypass authentication or extract arbitrary XML data.
|
|
285
|
+
**Fix:** Never concatenate user input into XPath expressions. Use parameterized XPath if your library supports it.
|
|
286
|
+
|
|
287
|
+
### Insecure Cookie (HIGH)
|
|
288
|
+
**Grep signature:** `httpOnly: false`
|
|
289
|
+
**Why it matters:** Cookies without `httpOnly` are readable via JavaScript, enabling session theft through XSS.
|
|
290
|
+
**Fix:** Set `httpOnly: true` on all session and auth cookies. Use `secure: true` in production.
|
|
291
|
+
|
|
292
|
+
### Credentials in AI Prompt (HIGH)
|
|
293
|
+
**Grep signature:** `system_prompt = "...mongodb://user:pass@..."`, `prompt += "...postgresql://...@..."`
|
|
294
|
+
**Why it matters:** Database connection strings with embedded passwords sent to external AI APIs expose credentials to the provider and any prompt logs.
|
|
295
|
+
**Fix:** Never include connection strings or credentials in prompts. Pass only sanitized, context-free data.
|
|
296
|
+
|
|
297
|
+
### LangChain Experimental (HIGH)
|
|
298
|
+
**Grep signature:** `from langchain_experimental`, `from 'langchain/experimental'`
|
|
299
|
+
**Why it matters:** The `langchain_experimental` package contains agents and chains with known RCE risk (PALChain, SQLDatabaseChain without sandboxing). It explicitly carries an "experimental" security disclaimer.
|
|
300
|
+
**Fix:** Audit each class imported from `langchain_experimental`. Replace PALChain/LLMMathChain with safe alternatives.
|
|
301
|
+
|
|
302
|
+
### Hardcoded HuggingFace Token (HIGH)
|
|
303
|
+
**Grep signature:** `'hf_...'` (≥30 chars)
|
|
304
|
+
**Note:** `skipInTests: true`
|
|
305
|
+
**Why it matters:** A committed HuggingFace token grants write access to model repos and private datasets.
|
|
306
|
+
**Fix:** Use `process.env.HF_TOKEN`. Rotate via huggingface.co/settings/tokens.
|
|
307
|
+
|
|
308
|
+
### NEXT_PUBLIC Secret (HIGH)
|
|
309
|
+
**Grep signature:** `NEXT_PUBLIC_SECRET_KEY`, `NEXT_PUBLIC_API_KEY`, `NEXT_PUBLIC_TOKEN`, etc. in code and `.env*` files
|
|
310
|
+
**Note:** `skipInTests: true`; also checked in `.env`, `.env.local`, `.env.production`, `.env.development`
|
|
311
|
+
**Why it matters:** Variables prefixed with `NEXT_PUBLIC_` are inlined into the client-side JavaScript bundle at build time. Any secret with this prefix is shipped to every browser.
|
|
312
|
+
**Fix:** Remove `NEXT_PUBLIC_` prefix from secret variables. Access them only server-side via `getServerSideProps` or API routes.
|
|
313
|
+
|
|
314
|
+
### Trojan Source (HIGH)
|
|
315
|
+
**Grep signature:** Unicode bidi control characters (U+202A–U+202E, U+2066–U+2069) in source code
|
|
316
|
+
**Why it matters:** CVE-2021-42574 — bidi control characters cause the code displayed in editors/diffs to differ from what the compiler executes, hiding malicious logic in plain sight.
|
|
317
|
+
**Fix:** Configure your editor and linter to detect and reject bidi characters in source files.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Prompt / Skill / Agent Patterns (expanded)
|
|
322
|
+
|
|
323
|
+
### MCP Tool Poisoning (HIGH)
|
|
324
|
+
**Grep signature:** `"description": "ignore previous instructions..."`, `"description": "override instructions..."`
|
|
325
|
+
**Why it matters:** Malicious MCP servers embed instructions in tool description fields. When an AI agent reads the tool list, it executes the injected instructions — redirecting actions, exfiltrating data, or bypassing safety checks.
|
|
326
|
+
**Fix:** Audit all MCP server tool descriptions. Use only servers from trusted sources. Pin MCP servers to verified commits.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Supply Chain / Package Patterns
|
|
331
|
+
|
|
332
|
+
### Supply Chain Exfiltration (CRITICAL)
|
|
333
|
+
**Files checked:** `package.json` `scripts.postinstall` / `scripts.preinstall`
|
|
334
|
+
**Grep signature:** `"postinstall": "curl https://..."`, `"preinstall": "wget http://..."`
|
|
335
|
+
**Why it matters:** A postinstall script that shells out to `curl`/`wget` can silently exfiltrate environment variables, `.env` files, or SSH keys to an attacker's server the moment anyone installs your package or its parent.
|
|
336
|
+
**Fix:** Remove network calls from lifecycle scripts. If data collection is needed, make it explicit and user-consented, never automatic on install.
|
package/lib/scanner.js
CHANGED
|
@@ -42,9 +42,31 @@ const VULN_PATTERNS = [
|
|
|
42
42
|
{ name: 'JWT Alg None', severity: 'CRITICAL', pattern: /algorithm\s*:\s*['"]none['"]/i },
|
|
43
43
|
{ name: 'Timing-Unsafe Comparison',severity: 'HIGH', pattern: /\b(?:token|password|secret|hash|digest|hmac|signature|api.?key)\w*\s*={2,3}\s*\w|(?:req\.(?:headers?|body|query|params)\.\w+)\s*={2,3}/i },
|
|
44
44
|
{ name: 'ReDoS', severity: 'HIGH', pattern: /new\s+RegExp\s*\(\s*req\.(?:query|body|params)\./i },
|
|
45
|
+
// ── AI / LLM Security ───────────────────────────────────────────────────────
|
|
46
|
+
{ name: 'LLM Prompt Injection', severity: 'CRITICAL', pattern: /\{\s*role\s*:\s*['"](?:user|system)['"]\s*,\s*content\s*:\s*req\.(body|query|params)|messages\b[^;\n]{0,100}push\s*\([^)]*req\.(body|query|params)/i },
|
|
47
|
+
{ name: 'LLM Output Execution', severity: 'CRITICAL', pattern: /\beval\s*\(\s*(?:await\s+)?(?:response|result|output|completion|generated|llmResult|aiResult)\b/i },
|
|
48
|
+
{ name: 'LangChain ShellTool', severity: 'CRITICAL', pattern: /\bShellTool\s*\(\)|LLMMathChain\.from_llm\s*\(|PALChain\.from_llm\s*\(/i },
|
|
49
|
+
{ name: 'Dynamic Require', severity: 'CRITICAL', pattern: /\brequire\s*\(\s*req\.(query|body|params)\./i },
|
|
50
|
+
{ name: 'VM Code Injection', severity: 'CRITICAL', pattern: /\bvm\.(runInNewContext|runInContext|runInThisContext)\s*\(\s*req\.(body|query|params)/i },
|
|
51
|
+
{ name: 'node-serialize RCE', severity: 'CRITICAL', pattern: /require\s*\(\s*['"]node-serialize['"]\s*\)/ },
|
|
52
|
+
{ name: 'Electron nodeIntegration', severity: 'CRITICAL', pattern: /\bnodeIntegration\s*:\s*true\b/ },
|
|
53
|
+
{ name: 'Electron webSecurity Off', severity: 'CRITICAL', pattern: /\bwebSecurity\s*:\s*false\b/ },
|
|
54
|
+
{ name: 'GitHub Actions Injection', severity: 'CRITICAL', pattern: /\$\{\{\s*github\.(event\.(pull_request\.(title|body)|issue\.(title|body)|comment\.body|review\.body)|head_ref)\s*\}\}/ },
|
|
55
|
+
{ name: 'Hardcoded OpenAI Key', severity: 'CRITICAL', skipInTests: true, pattern: /['"]sk-(?:proj-[A-Za-z0-9_\-]{40,}|[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9_\-]{20,})['"]/ },
|
|
56
|
+
{ name: 'Hardcoded Anthropic Key', severity: 'CRITICAL', skipInTests: true, pattern: /['"]sk-ant-api03-[A-Za-z0-9_\-]{10,}['"]/ },
|
|
57
|
+
// ── HIGH — web / protocol / AI ──────────────────────────────────────────────
|
|
58
|
+
{ name: 'Header Injection', severity: 'HIGH', pattern: /res\.(?:setHeader|set)\s*\([^,]+,\s*req\.(body|query|params)\b/i },
|
|
59
|
+
{ name: 'XPath Injection', severity: 'HIGH', pattern: /xpath\.(?:select|evaluate|selectNodes?)\s*\([^)]*req\.(query|body|params)/i },
|
|
60
|
+
{ name: 'Insecure Cookie', severity: 'HIGH', pattern: /\bhttpOnly\s*:\s*false\b/ },
|
|
61
|
+
{ name: 'Credentials in AI Prompt', severity: 'HIGH', pattern: /(?:system_prompt|systemPrompt|system|prompt|instruction)\s*[=:+][^;\n]{0,120}(?:mongodb(?:\+srv)?|postgresql?|mysql|redis):\/\/[a-zA-Z0-9_\-]+:[^@\s]{3,}@/ },
|
|
62
|
+
{ name: 'LangChain Experimental', severity: 'HIGH', pattern: /from\s+langchain_experimental\b|from\s+['"]langchain\/experimental['"]/i },
|
|
63
|
+
{ name: 'Hardcoded HuggingFace Token',severity: 'HIGH', skipInTests: true, pattern: /['"]hf_[A-Za-z0-9]{30,}['"]/ },
|
|
64
|
+
{ name: 'NEXT_PUBLIC Secret', severity: 'HIGH', skipInTests: true, pattern: /\bNEXT_PUBLIC_\w*(?:SECRET|PRIVATE|API_KEY|TOKEN|PASSWORD|CREDENTIAL)\w*/i },
|
|
65
|
+
{ name: 'Electron contextIsolation Off', severity: 'HIGH', pattern: /\bcontextIsolation\s*:\s*false\b/ },
|
|
66
|
+
{ name: 'Trojan Source', severity: 'HIGH', pattern: /[\u202A-\u202E\u2066-\u2069]/ },
|
|
45
67
|
];
|
|
46
68
|
|
|
47
|
-
const SCAN_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.go', '.dart']);
|
|
69
|
+
const SCAN_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.go', '.dart', '.yml', '.yaml']);
|
|
48
70
|
|
|
49
71
|
/** Maximum file size to read before skipping (512 KB). Prevents OOM on large generated files. */
|
|
50
72
|
const MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
@@ -55,6 +77,7 @@ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'ou
|
|
|
55
77
|
const PROMPT_PATTERNS = [
|
|
56
78
|
{ name: 'Deprecated CSRF Package', severity: 'CRITICAL', pattern: /\bcsurf\b/, skipCommentLine: true },
|
|
57
79
|
{ name: 'Unpinned npx MCP Server', severity: 'HIGH', pattern: /"command"\s*:\s*"npx"/ },
|
|
80
|
+
{ name: 'MCP Tool Poisoning', severity: 'HIGH', pattern: /"description"\s*:\s*"[^"]*(?:ignore (?:previous|all)|override (?:previous )?instructions?|disregard|forget (?:all )?(?:previous )?instructions?|you are now|new instructions?)/i },
|
|
58
81
|
{ name: 'Cleartext URL in Prompt', severity: 'MEDIUM', pattern: /\bhttp:\/\/(?!localhost|127\.0\.0\.1|169\.254\.)[a-zA-Z0-9]/ },
|
|
59
82
|
];
|
|
60
83
|
|
|
@@ -355,6 +378,74 @@ function scanAndroidManifest(projectDir) {
|
|
|
355
378
|
return findings;
|
|
356
379
|
}
|
|
357
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Scan package.json for supply-chain exfiltration: postinstall/preinstall scripts
|
|
383
|
+
* that shell out to curl/wget, which can silently steal data at install time.
|
|
384
|
+
* @param {string} projectDir - project root
|
|
385
|
+
* @returns {Array}
|
|
386
|
+
*/
|
|
387
|
+
function scanPackageJson(projectDir) {
|
|
388
|
+
const findings = [];
|
|
389
|
+
const filePath = path.join(projectDir, 'package.json');
|
|
390
|
+
if (!fs.existsSync(filePath)) return findings;
|
|
391
|
+
let lines;
|
|
392
|
+
try { lines = fs.readFileSync(filePath, 'utf8').split('\n'); } catch { return findings; }
|
|
393
|
+
const supplyChainRe = /["'](?:postinstall|preinstall)["']\s*:\s*["'][^"']*(?:curl|wget)\s+https?:\/\//i;
|
|
394
|
+
for (let i = 0; i < lines.length; i++) {
|
|
395
|
+
if (supplyChainRe.test(lines[i])) {
|
|
396
|
+
findings.push({
|
|
397
|
+
severity: 'CRITICAL',
|
|
398
|
+
name: 'Supply Chain Exfiltration',
|
|
399
|
+
file: 'package.json',
|
|
400
|
+
line: i + 1,
|
|
401
|
+
snippet: lines[i].trim().slice(0, 80),
|
|
402
|
+
inTestFile: false,
|
|
403
|
+
likelyFalsePositive: false,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return findings;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Scan .env files for NEXT_PUBLIC_ variables containing secrets.
|
|
412
|
+
* NEXT_PUBLIC_ variables are inlined into the client-side JS bundle at build
|
|
413
|
+
* time, so any secret stored with this prefix is exposed to all browsers.
|
|
414
|
+
* @param {string} projectDir - project root
|
|
415
|
+
* @returns {Array}
|
|
416
|
+
*/
|
|
417
|
+
function scanEnvFiles(projectDir) {
|
|
418
|
+
const findings = [];
|
|
419
|
+
const candidates = ['.env', '.env.local', '.env.development', '.env.production', '.env.test', '.env.staging'];
|
|
420
|
+
const nextPublicSecretRe = /^NEXT_PUBLIC_\w*(?:SECRET|PRIVATE|API_KEY|TOKEN|PASSWORD|CREDENTIAL)\w*\s*=/i;
|
|
421
|
+
for (const name of candidates) {
|
|
422
|
+
const filePath = path.join(projectDir, name);
|
|
423
|
+
if (!fs.existsSync(filePath)) continue;
|
|
424
|
+
let content;
|
|
425
|
+
try {
|
|
426
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
427
|
+
if (content.length > MAX_SCAN_FILE_BYTES) continue;
|
|
428
|
+
} catch { continue; }
|
|
429
|
+
const lines = content.split('\n');
|
|
430
|
+
for (let i = 0; i < lines.length; i++) {
|
|
431
|
+
const line = lines[i];
|
|
432
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
433
|
+
if (nextPublicSecretRe.test(line)) {
|
|
434
|
+
findings.push({
|
|
435
|
+
severity: 'HIGH',
|
|
436
|
+
name: 'NEXT_PUBLIC Secret',
|
|
437
|
+
file: name,
|
|
438
|
+
line: i + 1,
|
|
439
|
+
snippet: line.trim().slice(0, 80),
|
|
440
|
+
inTestFile: false,
|
|
441
|
+
likelyFalsePositive: false,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return findings;
|
|
447
|
+
}
|
|
448
|
+
|
|
358
449
|
// ─── Quick Scan ───────────────────────────────────────────────────────────────
|
|
359
450
|
|
|
360
451
|
/**
|
|
@@ -396,7 +487,7 @@ function quickScan(projectDir) {
|
|
|
396
487
|
}
|
|
397
488
|
}
|
|
398
489
|
}
|
|
399
|
-
return [...findings, ...scanAppConfig(projectDir), ...scanAndroidManifest(projectDir), ...scanPromptFiles(projectDir)];
|
|
490
|
+
return [...findings, ...scanAppConfig(projectDir), ...scanAndroidManifest(projectDir), ...scanPromptFiles(projectDir), ...scanPackageJson(projectDir), ...scanEnvFiles(projectDir)];
|
|
400
491
|
}
|
|
401
492
|
|
|
402
493
|
// ─── Print Findings ───────────────────────────────────────────────────────────
|
|
@@ -462,6 +553,8 @@ module.exports = {
|
|
|
462
553
|
hasSafeAuditStatus,
|
|
463
554
|
scanAppConfig,
|
|
464
555
|
scanAndroidManifest,
|
|
556
|
+
scanPackageJson,
|
|
557
|
+
scanEnvFiles,
|
|
465
558
|
scanPromptFiles,
|
|
466
559
|
quickScan,
|
|
467
560
|
printFindings,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lhi/tdd-audit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "Security skill installer for Claude Code, Gemini CLI, Cursor, Codex, and OpenCode. Patches vulnerabilities using a Red-Green-Refactor exploit-test protocol.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -50,6 +50,14 @@
|
|
|
50
50
|
},
|
|
51
51
|
"author": "Kyra Lee",
|
|
52
52
|
"license": "MIT",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/kyralee2992/tdd-remediation-skill.git"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/kyralee2992/tdd-remediation-skill#readme",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/kyralee2992/tdd-remediation-skill/issues"
|
|
60
|
+
},
|
|
53
61
|
"devDependencies": {
|
|
54
62
|
"jest": "^30.3.0"
|
|
55
63
|
}
|
package/prompts/auto-audit.md
CHANGED
|
@@ -247,6 +247,80 @@ resolve_entities.*True # Python lxml entity expansion
|
|
|
247
247
|
# bundle audit
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
+
**AI / LLM Security (check when the project uses OpenAI, Anthropic, LangChain, or any LLM SDK)**
|
|
251
|
+
```
|
|
252
|
+
role.*content.*req\. # LLM Prompt Injection — user input in messages array
|
|
253
|
+
messages.*push.*req\. # LLM Prompt Injection — appending request data to LLM context
|
|
254
|
+
eval\(.*response # LLM Output Execution — evaluating model output
|
|
255
|
+
eval\(.*result # LLM Output Execution — evaluating model result
|
|
256
|
+
eval\(.*completion # LLM Output Execution — evaluating AI completion
|
|
257
|
+
ShellTool\(\) # LangChain ShellTool — shell command execution
|
|
258
|
+
LLMMathChain\.from_llm # LangChain math eval — known RCE (CVE-2023-29374)
|
|
259
|
+
PALChain\.from_llm # LangChain PAL — eval of LLM-generated Python
|
|
260
|
+
require\(req\. # Dynamic Require — loading user-controlled modules
|
|
261
|
+
vm\.runIn.*Context.*req\. # VM Code Injection — sandbox escape risk
|
|
262
|
+
require.*node-serialize # node-serialize RCE — known deserialization vulnerability
|
|
263
|
+
langchain_experimental # LangChain Experimental — contains RCE-risk components
|
|
264
|
+
system_prompt.*mongodb:// # Credentials in AI Prompt — DB URL in prompt context
|
|
265
|
+
prompt.*postgresql:// # Credentials in AI Prompt — DB URL in prompt context
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Hardcoded AI API Keys**
|
|
269
|
+
```
|
|
270
|
+
sk-proj- # OpenAI new-format key (≥60 chars)
|
|
271
|
+
T3BlbkFJ # OpenAI old-format key marker (base64 of "OpenAI")
|
|
272
|
+
sk-ant-api03- # Anthropic API key prefix
|
|
273
|
+
hf_[A-Za-z0-9]{30,} # HuggingFace token (≥30 chars after hf_)
|
|
274
|
+
NEXT_PUBLIC_.*SECRET # Next.js client-bundled secret variable
|
|
275
|
+
NEXT_PUBLIC_.*API_KEY # Next.js client-bundled API key
|
|
276
|
+
NEXT_PUBLIC_.*TOKEN # Next.js client-bundled token
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**GitHub Actions Injection (scan .github/workflows/*.yml)**
|
|
280
|
+
```
|
|
281
|
+
github\.event\.pull_request\.title # Attacker-controlled PR title in run: step
|
|
282
|
+
github\.event\.pull_request\.body # Attacker-controlled PR body in run: step
|
|
283
|
+
github\.event\.issue\.title # Attacker-controlled issue title in run: step
|
|
284
|
+
github\.event\.issue\.body # Attacker-controlled issue body in run: step
|
|
285
|
+
github\.event\.comment\.body # Attacker-controlled comment body in run: step
|
|
286
|
+
github\.head_ref # Attacker-controlled branch name in run: step
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Electron Security (check main process and BrowserWindow config)**
|
|
290
|
+
```
|
|
291
|
+
nodeIntegration.*true # CRITICAL: enables Node.js in renderer — XSS → full system compromise
|
|
292
|
+
webSecurity.*false # CRITICAL: disables same-origin policy in renderer
|
|
293
|
+
contextIsolation.*false # HIGH: allows prototype pollution from web content
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Supply Chain (check package.json)**
|
|
297
|
+
```
|
|
298
|
+
postinstall.*curl # Supply Chain Exfiltration — curl in postinstall script
|
|
299
|
+
preinstall.*curl # Supply Chain Exfiltration — curl in preinstall script
|
|
300
|
+
postinstall.*wget # Supply Chain Exfiltration — wget in postinstall script
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Web / Protocol Injection**
|
|
304
|
+
```
|
|
305
|
+
res\.setHeader.*req\. # Header Injection — user input in response header value
|
|
306
|
+
xpath\.select.*req\. # XPath Injection — user input in XPath query
|
|
307
|
+
httpOnly.*false # Insecure Cookie — session cookie readable via JavaScript
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Trojan Source (use Grep with unicode flag if available)**
|
|
311
|
+
```
|
|
312
|
+
\u202[A-E]|\u206[6-9] # Bidi control characters — visual/compiled mismatch (CVE-2021-42574)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Dependency Audit**
|
|
316
|
+
```
|
|
317
|
+
# Run manually — not grep-based:
|
|
318
|
+
# npm audit --audit-level=high
|
|
319
|
+
# pip-audit
|
|
320
|
+
# govulncheck ./...
|
|
321
|
+
# bundle audit
|
|
322
|
+
```
|
|
323
|
+
|
|
250
324
|
### 0d. Audit Prompt & Skill Files
|
|
251
325
|
|
|
252
326
|
For projects that contain AI agent configurations, scan the following locations for prompt-specific vulnerabilities:
|
|
@@ -257,6 +331,8 @@ For projects that contain AI agent configurations, scan the following locations
|
|
|
257
331
|
|---|---|---|
|
|
258
332
|
| `csurf` package reference | CRITICAL | `csurf` was deprecated March 2023 and is unmaintained — use `csrf-csrf` instead |
|
|
259
333
|
| `"command": "npx"` in MCP config | HIGH | Unpinned npx MCP server executes whatever version npm resolves at runtime |
|
|
334
|
+
| `"description": "ignore previous instructions..."` | HIGH | MCP Tool Poisoning — malicious instructions embedded in tool description fields hijack agent behavior |
|
|
335
|
+
| `"description": "override instructions..."` | HIGH | MCP Tool Poisoning — agent reads tool list and executes injected instructions |
|
|
260
336
|
| `http://` URL (non-localhost) | MEDIUM | Cleartext URLs in prompts can mislead agents to make insecure requests |
|
|
261
337
|
| Prompt reads arbitrary user-controlled files without a guardrail | HIGH | AI reading untrusted file content without isolation is a prompt-injection risk (ASI01) |
|
|
262
338
|
|