@lhi/tdd-audit 1.5.0 → 1.8.2
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 +76 -23
- package/SKILL.md +1 -1
- package/docs/agentic-ai-security.md +202 -0
- package/docs/ci-cd.md +169 -0
- package/docs/hardening.md +267 -0
- package/docs/scanner.md +161 -0
- package/docs/tdd-protocol.md +184 -0
- package/docs/vulnerability-patterns.md +200 -0
- package/lib/scanner.js +71 -30
- package/package.json +3 -2
- package/workflows/tdd-audit.md +6 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Vulnerability Patterns Reference
|
|
2
|
+
|
|
3
|
+
All 34 patterns detected by `@lhi/tdd-audit`. Patterns are checked against every scannable source file line-by-line. Prompt/skill patterns are checked separately against `.md` files in agent configuration directories.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## CRITICAL
|
|
8
|
+
|
|
9
|
+
### SQL Injection
|
|
10
|
+
**Grep signature:** template literal SELECT, string-concatenated query, Python f-string/%-format SQL, tagged template DB call
|
|
11
|
+
**Why it matters:** Attacker can read, modify, or delete any data in your database by manipulating the query string.
|
|
12
|
+
**Fix:** Parameterized queries / ORM methods. See [`green-phase.md`](../prompts/green-phase.md#sql-injection).
|
|
13
|
+
|
|
14
|
+
### Command Injection
|
|
15
|
+
**Grep signature:** `exec(` / `execSync(` with `req.params|body|query`; `subprocess.run(shell=True)`
|
|
16
|
+
**Why it matters:** Attacker can run arbitrary shell commands on your server.
|
|
17
|
+
**Fix:** Use `execFile`/`spawn` with an argument array (no shell interpolation).
|
|
18
|
+
|
|
19
|
+
### TLS Bypass
|
|
20
|
+
**Grep signature:** `badCertificateCallback = true`, `rejectUnauthorized: false`, `NODE_TLS_REJECT_UNAUTHORIZED=0`
|
|
21
|
+
**Why it matters:** All HTTPS connections become vulnerable to man-in-the-middle attacks.
|
|
22
|
+
**Fix:** Remove the override. For internal CAs, set `NODE_EXTRA_CA_CERTS` or pass the cert to `SecurityContext`.
|
|
23
|
+
|
|
24
|
+
### Hardcoded Secret
|
|
25
|
+
**Grep signature:** `const API_KEY = "..."`, `let SECRET_KEY = "..."` (≥20 chars)
|
|
26
|
+
**Note:** `skipInTests: true` — matches in test files are marked `likelyFalsePositive`.
|
|
27
|
+
**Why it matters:** Secret is committed to git history and visible to anyone with repo access.
|
|
28
|
+
**Fix:** Move to environment variables. Run `gitleaks` to check if already committed.
|
|
29
|
+
|
|
30
|
+
### SSRF (Server-Side Request Forgery)
|
|
31
|
+
**Grep signature:** `fetch(req.query.url)`, `axios.get(req.body.url)`, `got(req.params.url)`
|
|
32
|
+
**Why it matters:** Attacker can probe internal services (AWS metadata, Redis, internal APIs) via your server.
|
|
33
|
+
**Fix:** Validate URL against an explicit hostname allowlist. Block private IP ranges.
|
|
34
|
+
|
|
35
|
+
### Insecure Deserialization
|
|
36
|
+
**Grep signature:** `.unserialize(req.)`, `__proto__ =`, `Object.setPrototypeOf(x, req.`
|
|
37
|
+
**Why it matters:** Attacker can achieve RCE or privilege escalation by crafting a malicious serialized payload.
|
|
38
|
+
**Fix:** Never deserialize user-supplied data. Use JSON with a schema validator instead.
|
|
39
|
+
|
|
40
|
+
### JWT Alg None
|
|
41
|
+
**Grep signature:** `algorithm: 'none'`
|
|
42
|
+
**Why it matters:** The `alg:none` attack strips the JWT signature entirely, allowing anyone to forge tokens.
|
|
43
|
+
**Fix:** Use `jsonwebtoken` with an explicit `algorithms` allowlist — never include `'none'`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## HIGH
|
|
48
|
+
|
|
49
|
+
### IDOR (Insecure Direct Object Reference)
|
|
50
|
+
**Grep signature:** `findById(req.params|body|query.`, `findOne({id: req.params|body|query`
|
|
51
|
+
**Why it matters:** Any logged-in user can access another user's private data by guessing or iterating IDs.
|
|
52
|
+
**Fix:** Scope all DB queries to `req.user.id`. Never trust a client-supplied resource ID.
|
|
53
|
+
|
|
54
|
+
### XSS (Cross-Site Scripting)
|
|
55
|
+
**Grep signature:** `innerHTML =`, `dangerouslySetInnerHTML={{`, `document.write(`, `res.send(\`...\${req.`
|
|
56
|
+
**Why it matters:** Attacker can inject scripts that run in other users' browsers, stealing sessions or redirecting them.
|
|
57
|
+
**Fix:** Escape on output (`escape-html`), sanitize rich HTML (`DOMPurify`), or use a framework that auto-escapes.
|
|
58
|
+
|
|
59
|
+
### Path Traversal
|
|
60
|
+
**Grep signature:** `readFile/sendFile/createReadStream(req.`, `path.join(req.params|body|query`
|
|
61
|
+
**Why it matters:** Attacker can read files outside the uploads directory (`.env`, `/etc/passwd`).
|
|
62
|
+
**Fix:** `path.resolve()` the final path and assert it starts with the allowed base directory.
|
|
63
|
+
|
|
64
|
+
### Broken Auth
|
|
65
|
+
**Grep signature:** `jwt.decode(` (without `.verify`), `verify: false`, `secret = "short_string"`
|
|
66
|
+
**Why it matters:** Anyone can forge a valid-looking token and impersonate any user.
|
|
67
|
+
**Fix:** Always use `jwt.verify()` with an explicit secret from environment variables.
|
|
68
|
+
|
|
69
|
+
### Sensitive Storage
|
|
70
|
+
**Grep signature:** `localStorage.setItem('token'`, `AsyncStorage.setItem('token'`
|
|
71
|
+
**Why it matters:** Tokens stored in unencrypted storage are readable on rooted/jailbroken devices and via XSS.
|
|
72
|
+
**Fix:** Use `expo-secure-store` (React Native/Expo) or `flutter_secure_storage` (Flutter).
|
|
73
|
+
|
|
74
|
+
### eval() Injection
|
|
75
|
+
**Grep signature:** `eval(route.params`, `eval(searchParams.get`, `eval(req.query|body`
|
|
76
|
+
**Why it matters:** Attacker can execute arbitrary JavaScript in the application context.
|
|
77
|
+
**Fix:** Never use `eval()` with user input. Use `JSON.parse()` for data deserialization.
|
|
78
|
+
|
|
79
|
+
### Insecure Random
|
|
80
|
+
**Grep signature:** `token = Math.random()`, `sessionId = Math.random()`
|
|
81
|
+
**Why it matters:** `Math.random()` is not cryptographically secure — tokens can be predicted.
|
|
82
|
+
**Fix:** Use `crypto.randomBytes()` (Node.js) or `secrets.token_hex()` (Python).
|
|
83
|
+
|
|
84
|
+
### Secret Fallback
|
|
85
|
+
**Grep signature:** `process.env.SECRET || "hardcoded_value"`
|
|
86
|
+
**Why it matters:** The hardcoded fallback is committed to source control and used whenever the env var is missing.
|
|
87
|
+
**Fix:** Fail fast if the env var is absent — never fall back to a default secret.
|
|
88
|
+
|
|
89
|
+
### Open Redirect
|
|
90
|
+
**Grep signature:** `res.redirect(req.query|body|params.`, `window.location = params.`
|
|
91
|
+
**Why it matters:** Attacker can redirect users to phishing sites after a legitimate login flow.
|
|
92
|
+
**Fix:** Allow only relative paths. Reject `http://` / `https://` and `//` prefix destinations.
|
|
93
|
+
|
|
94
|
+
### NoSQL Injection
|
|
95
|
+
**Grep signature:** `.find(req.body|query)`, `.findOne(req.body|query)`, `$where:`
|
|
96
|
+
**Why it matters:** Attacker can bypass authentication by injecting MongoDB operators (`{ $gt: '' }`).
|
|
97
|
+
**Fix:** Cast query values to strings. Use `express-mongo-sanitize` to strip `$` operators.
|
|
98
|
+
|
|
99
|
+
### Template Injection
|
|
100
|
+
**Grep signature:** `res.render(req.params|query`, `ejs.render(req.body`, `pug.render(req.body`
|
|
101
|
+
**Why it matters:** Attacker can execute server-side template code, potentially achieving RCE.
|
|
102
|
+
**Fix:** Never pass user input as the template name or raw template string.
|
|
103
|
+
|
|
104
|
+
### Mass Assignment
|
|
105
|
+
**Grep signature:** `new Model(req.body)`, `.create(req.body)`, `.update({}, req.body)`
|
|
106
|
+
**Why it matters:** Attacker can set privileged fields (`isAdmin`, `role`) by adding them to a POST body.
|
|
107
|
+
**Fix:** Destructure and allowlist only the fields users are permitted to set.
|
|
108
|
+
|
|
109
|
+
### Prototype Pollution
|
|
110
|
+
**Grep signature:** `_.merge(req.body|query)`, `deepmerge(req.body|query)`, `Object.assign({}, req.body)`
|
|
111
|
+
**Why it matters:** Attacker can inject properties into `Object.prototype`, affecting all objects in the process.
|
|
112
|
+
**Fix:** Sanitize `__proto__` / `constructor` / `prototype` keys before any recursive merge.
|
|
113
|
+
|
|
114
|
+
### Weak Crypto
|
|
115
|
+
**Grep signature:** `createHash('md5')`, `createHash('sha1')`, `md5(password)`, `sha1(password)`
|
|
116
|
+
**Why it matters:** MD5 and SHA1 hashes are trivially crackable with rainbow tables.
|
|
117
|
+
**Fix:** Use `bcrypt` (cost factor ≥12) or `argon2` for passwords.
|
|
118
|
+
|
|
119
|
+
### XXE (XML External Entity)
|
|
120
|
+
**Grep signature:** `noent: true`, `expand_entities = True`, `resolve_entities = True`
|
|
121
|
+
**Why it matters:** Attacker can read local files or perform SSRF via XML entity expansion.
|
|
122
|
+
**Fix:** Disable entity expansion in your XML parser. Never enable it for user-supplied XML.
|
|
123
|
+
|
|
124
|
+
### WebView JS Bridge
|
|
125
|
+
**Grep signature:** `addJavascriptInterface(`, `javaScriptEnabled: true`, `allowFileAccess: true`, `allowUniversalAccessFromFileURLs: true`
|
|
126
|
+
**Why it matters:** Exposed JavaScript bridge or relaxed WebView settings allow XSS-to-native escalation.
|
|
127
|
+
**Fix:** Disable unnecessary WebView capabilities. Never expose a JS bridge to untrusted content.
|
|
128
|
+
|
|
129
|
+
### Timing-Unsafe Comparison
|
|
130
|
+
**Grep signature:** `token === `, `password ===`, `secret ==` (equality comparison of secrets)
|
|
131
|
+
**Why it matters:** Timing side-channel allows attackers to brute-force tokens bit by bit.
|
|
132
|
+
**Fix:** Use `crypto.timingSafeEqual()` (Node.js) or `hmac.compare_digest()` (Python) for all secret comparisons.
|
|
133
|
+
|
|
134
|
+
### ReDoS
|
|
135
|
+
**Grep signature:** `new RegExp(req.query|body|params.`
|
|
136
|
+
**Why it matters:** Attacker can craft input that causes catastrophic regex backtracking, DoSing the process.
|
|
137
|
+
**Fix:** Never construct regex from user input. If required, use a regex complexity validator.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## MEDIUM
|
|
142
|
+
|
|
143
|
+
### Sensitive Log
|
|
144
|
+
**Grep signature:** `console.log(token|password|secret|jwt|authorization|apiKey`
|
|
145
|
+
**Note:** `skipInTests: true`
|
|
146
|
+
**Why it matters:** Secrets end up in log aggregation systems, monitoring dashboards, and CI output.
|
|
147
|
+
**Fix:** Remove or redact sensitive fields before logging.
|
|
148
|
+
|
|
149
|
+
### CORS Wildcard
|
|
150
|
+
**Grep signature:** `cors({ origin: '*' })`, `Access-Control-Allow-Origin: *`
|
|
151
|
+
**Why it matters:** Any origin can make credentialed requests to your API.
|
|
152
|
+
**Fix:** Specify an explicit origin allowlist in your CORS configuration.
|
|
153
|
+
|
|
154
|
+
### Cleartext Traffic
|
|
155
|
+
**Grep signature:** `baseURL = 'http://...'` (non-localhost)
|
|
156
|
+
**Note:** `skipInTests: true`
|
|
157
|
+
**Why it matters:** API traffic is sent unencrypted and visible to network observers.
|
|
158
|
+
**Fix:** Use `https://` for all non-localhost API base URLs.
|
|
159
|
+
|
|
160
|
+
### Deep Link Injection
|
|
161
|
+
**Grep signature:** `Linking.getInitialURL()`, `Linking.addEventListener('url'`
|
|
162
|
+
**Why it matters:** Attacker can inject malicious data via crafted deep links if parameters are not validated.
|
|
163
|
+
**Fix:** Validate and sanitize all values extracted from deep link URLs before use.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Prompt / Skill / Agent Patterns
|
|
168
|
+
|
|
169
|
+
These patterns are checked against `.md` files in `prompts/`, `skills/`, `.claude/`, `workflows/`, `CLAUDE.md`, `SKILL.md`, `.cursorrules`, and `.clinerules`.
|
|
170
|
+
|
|
171
|
+
### Deprecated CSRF Package (CRITICAL)
|
|
172
|
+
**Grep signature:** `\bcsurf\b` (not in a comment line)
|
|
173
|
+
**Why it matters:** `csurf` was deprecated in March 2023 and is unmaintained. Projects that follow instructions referencing it will install a package with unpatched vulnerabilities.
|
|
174
|
+
**Fix:** Replace with `csrf-csrf` (`doubleCsrf` pattern).
|
|
175
|
+
|
|
176
|
+
### Unpinned npx MCP Server (HIGH)
|
|
177
|
+
**Grep signature:** `"command": "npx"` in MCP server config
|
|
178
|
+
**Why it matters:** `npx` resolves the latest version at runtime. A compromised package version executes arbitrary code in the agent's context.
|
|
179
|
+
**Fix:** Pin MCP servers to exact versions or install locally. Use `node /path/to/server.js` instead of `npx`.
|
|
180
|
+
|
|
181
|
+
### Cleartext URL in Prompt (MEDIUM)
|
|
182
|
+
**Grep signature:** `http://` (non-localhost) in prompt/skill markdown
|
|
183
|
+
**Why it matters:** Cleartext URLs in agent instructions can mislead the agent into making insecure HTTP requests.
|
|
184
|
+
**Fix:** Replace with `https://` URLs.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Config / Manifest Patterns
|
|
189
|
+
|
|
190
|
+
### Config Secret (CRITICAL)
|
|
191
|
+
**Files checked:** `app.json`, `app.config.js`, `app.config.ts`
|
|
192
|
+
**Grep signature:** `apiKey: "..."`, `secret: "..."`, `accessToken: "..."` (≥20 chars)
|
|
193
|
+
**Why it matters:** Expo/React Native config files are bundled into the app binary and shipped to users.
|
|
194
|
+
**Fix:** Use `expo-constants` with environment variables at build time. Never embed secrets in config files.
|
|
195
|
+
|
|
196
|
+
### Android Debuggable (HIGH)
|
|
197
|
+
**Files checked:** `android/app/src/main/AndroidManifest.xml`
|
|
198
|
+
**Grep signature:** `android:debuggable="true"`
|
|
199
|
+
**Why it matters:** Debug builds expose the app to `adb` inspection and arbitrary code injection on the device.
|
|
200
|
+
**Fix:** Remove `android:debuggable` from `AndroidManifest.xml` (the build system sets it correctly per variant).
|
package/lib/scanner.js
CHANGED
|
@@ -45,6 +45,9 @@ const VULN_PATTERNS = [
|
|
|
45
45
|
];
|
|
46
46
|
|
|
47
47
|
const SCAN_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.go', '.dart']);
|
|
48
|
+
|
|
49
|
+
/** Maximum file size to read before skipping (512 KB). Prevents OOM on large generated files. */
|
|
50
|
+
const MAX_SCAN_FILE_BYTES = 512 * 1024;
|
|
48
51
|
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', '__pycache__', 'venv', '.venv', 'vendor', '.expo', '.dart_tool', '.pub-cache']);
|
|
49
52
|
|
|
50
53
|
// ─── Prompt / Skill Patterns ──────────────────────────────────────────────────
|
|
@@ -214,14 +217,22 @@ function hasSafeAuditStatus(lines) {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
/**
|
|
217
|
-
* Returns true if the match at matchIndex falls inside a backtick
|
|
218
|
-
*
|
|
220
|
+
* Returns true if the match at matchIndex falls inside a *closed* backtick
|
|
221
|
+
* code span on the same line. A code span is closed only when there is an
|
|
222
|
+
* odd number of backticks before the match AND at least one closing backtick
|
|
223
|
+
* after it on the same line. A lone, unmatched backtick before the pattern
|
|
224
|
+
* does NOT constitute a code span and must NOT suppress the finding.
|
|
219
225
|
* @param {string} line
|
|
220
226
|
* @param {number} matchIndex - character index of the match start
|
|
221
227
|
*/
|
|
222
228
|
function isInsideBackticks(line, matchIndex) {
|
|
223
229
|
const before = line.slice(0, matchIndex);
|
|
224
|
-
|
|
230
|
+
const after = line.slice(matchIndex);
|
|
231
|
+
const backticksBefore = (before.match(/`/g) || []).length;
|
|
232
|
+
const backticksAfter = (after.match(/`/g) || []).length;
|
|
233
|
+
// Suppress only when the span is properly closed: odd opening count + at
|
|
234
|
+
// least one closing backtick exists after the match position.
|
|
235
|
+
return backticksBefore % 2 === 1 && backticksAfter >= 1;
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
/**
|
|
@@ -234,16 +245,32 @@ function isCommentLine(line) {
|
|
|
234
245
|
|
|
235
246
|
/**
|
|
236
247
|
* Scan all prompt/skill .md files in projectDir for prompt-specific patterns.
|
|
248
|
+
*
|
|
249
|
+
* Returns a findings array with a non-enumerable `.exempted` property — an
|
|
250
|
+
* array of relative paths for files skipped via `audit_status: safe`. Using
|
|
251
|
+
* a non-enumerable property preserves full backward compatibility: spread,
|
|
252
|
+
* toEqual([]), and quickScan's `...scanPromptFiles()` all continue to work.
|
|
253
|
+
*
|
|
237
254
|
* @param {string} projectDir - project root
|
|
238
|
-
* @returns {Array} findings
|
|
255
|
+
* @returns {Array} findings (with non-enumerable .exempted: string[])
|
|
239
256
|
*/
|
|
240
257
|
function scanPromptFiles(projectDir) {
|
|
241
258
|
const findings = [];
|
|
259
|
+
const exempted = [];
|
|
242
260
|
for (const filePath of walkMdFiles(projectDir)) {
|
|
243
261
|
if (!isPromptFile(filePath, projectDir)) continue;
|
|
244
262
|
let lines;
|
|
245
|
-
try {
|
|
246
|
-
|
|
263
|
+
try {
|
|
264
|
+
// SEC-06: read first, then check length — eliminates statSync/readFileSync TOCTOU race.
|
|
265
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
266
|
+
if (content.length > MAX_SCAN_FILE_BYTES) continue;
|
|
267
|
+
if (content.includes('\0')) continue; // skip binary files (mirrors quickScan guard)
|
|
268
|
+
lines = content.split('\n');
|
|
269
|
+
} catch { continue; }
|
|
270
|
+
if (hasSafeAuditStatus(lines)) {
|
|
271
|
+
exempted.push(path.relative(projectDir, filePath));
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
247
274
|
for (let i = 0; i < lines.length; i++) {
|
|
248
275
|
for (const p of PROMPT_PATTERNS) {
|
|
249
276
|
const match = p.pattern.exec(lines[i]);
|
|
@@ -262,6 +289,8 @@ function scanPromptFiles(projectDir) {
|
|
|
262
289
|
}
|
|
263
290
|
}
|
|
264
291
|
}
|
|
292
|
+
// Attach exempted as non-enumerable so spread / toEqual([]) are unaffected.
|
|
293
|
+
Object.defineProperty(findings, 'exempted', { value: exempted, enumerable: false, configurable: true });
|
|
265
294
|
return findings;
|
|
266
295
|
}
|
|
267
296
|
|
|
@@ -339,8 +368,10 @@ function quickScan(projectDir) {
|
|
|
339
368
|
const inTest = isTestFile(filePath, projectDir);
|
|
340
369
|
let content;
|
|
341
370
|
// L1 fix: guard against binary / non-UTF-8 files
|
|
371
|
+
// SEC-06: read first, then check length — eliminates statSync/readFileSync TOCTOU race.
|
|
342
372
|
try {
|
|
343
373
|
content = fs.readFileSync(filePath, 'utf8');
|
|
374
|
+
if (content.length > MAX_SCAN_FILE_BYTES) continue;
|
|
344
375
|
} catch {
|
|
345
376
|
continue;
|
|
346
377
|
}
|
|
@@ -372,38 +403,47 @@ function quickScan(projectDir) {
|
|
|
372
403
|
|
|
373
404
|
/**
|
|
374
405
|
* Print a human-readable findings report to stdout.
|
|
375
|
-
* @param {Array}
|
|
406
|
+
* @param {Array} findings - array of finding objects
|
|
407
|
+
* @param {string[]} [exempted=[]] - relative paths of files skipped via audit_status:safe
|
|
376
408
|
*/
|
|
377
|
-
function printFindings(findings) {
|
|
409
|
+
function printFindings(findings, exempted = []) {
|
|
378
410
|
if (findings.length === 0) {
|
|
379
411
|
console.log(' ✅ No obvious vulnerability patterns detected.\n');
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
412
|
+
} else {
|
|
413
|
+
const real = findings.filter(f => !f.likelyFalsePositive);
|
|
414
|
+
const noisy = findings.filter(f => f.likelyFalsePositive);
|
|
415
|
+
|
|
416
|
+
const bySeverity = { CRITICAL: [], HIGH: [], MEDIUM: [], LOW: [] };
|
|
417
|
+
for (const f of real) (bySeverity[f.severity] || bySeverity.LOW).push(f);
|
|
418
|
+
const icons = { CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🔵' };
|
|
419
|
+
|
|
420
|
+
console.log(`\n Found ${real.length} potential issue(s)${noisy.length ? ` (+${noisy.length} in test files — see below)` : ''}:\n`);
|
|
421
|
+
for (const [sev, list] of Object.entries(bySeverity)) {
|
|
422
|
+
if (!list.length) continue;
|
|
423
|
+
for (const f of list) {
|
|
424
|
+
const testBadge = f.inTestFile ? ' [test file]' : '';
|
|
425
|
+
console.log(` ${icons[sev]} [${sev}] ${f.name} — ${f.file}:${f.line}${testBadge}`);
|
|
426
|
+
console.log(` ${f.snippet}`);
|
|
427
|
+
}
|
|
396
428
|
}
|
|
397
|
-
}
|
|
398
429
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
430
|
+
if (noisy.length) {
|
|
431
|
+
console.log('\n ⚪ Likely intentional (in test files — verify manually):');
|
|
432
|
+
for (const f of noisy) {
|
|
433
|
+
console.log(` ${f.name} — ${f.file}:${f.line}`);
|
|
434
|
+
}
|
|
403
435
|
}
|
|
436
|
+
|
|
437
|
+
console.log('\n Run /tdd-audit in your agent to remediate.\n');
|
|
404
438
|
}
|
|
405
439
|
|
|
406
|
-
|
|
440
|
+
if (exempted.length) {
|
|
441
|
+
console.log(' ⚠️ Files skipped via audit_status:safe (verify these exemptions are intentional):');
|
|
442
|
+
for (const p of exempted) {
|
|
443
|
+
console.log(` ${p}`);
|
|
444
|
+
}
|
|
445
|
+
console.log('');
|
|
446
|
+
}
|
|
407
447
|
}
|
|
408
448
|
|
|
409
449
|
module.exports = {
|
|
@@ -411,6 +451,7 @@ module.exports = {
|
|
|
411
451
|
PROMPT_PATTERNS,
|
|
412
452
|
SCAN_EXTENSIONS,
|
|
413
453
|
SKIP_DIRS,
|
|
454
|
+
MAX_SCAN_FILE_BYTES,
|
|
414
455
|
detectFramework,
|
|
415
456
|
detectAppFramework,
|
|
416
457
|
detectTestBaseDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lhi/tdd-audit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.2",
|
|
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": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"templates/",
|
|
15
15
|
"workflows/",
|
|
16
16
|
"README.md",
|
|
17
|
-
"LICENSE"
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"docs/"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
20
21
|
"test": "jest --forceExit",
|
package/workflows/tdd-audit.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Run the complete TDD Remediation Autonomous Audit
|
|
3
|
+
risk: low
|
|
4
|
+
source: personal
|
|
5
|
+
date_added: "2024-01-01"
|
|
6
|
+
audited_by: lcanady
|
|
7
|
+
last_audited: "2026-03-25"
|
|
8
|
+
audit_status: safe
|
|
3
9
|
---
|
|
4
10
|
Please use the TDD Remediation Protocol Auto-Audit skill (located in the `skills/tdd-remediation` folder) to secure this repository.
|
|
5
11
|
|