@lhi/tdd-audit 1.1.0 → 1.1.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 +1 -0
- package/index.js +45 -15
- package/package.json +1 -1
- package/prompts/auto-audit.md +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ node index.js
|
|
|
34
34
|
| `--claude` | Use `.claude/` instead of `.agents/` as the skill directory |
|
|
35
35
|
| `--with-hooks` | Install a pre-commit hook that blocks commits if security tests fail |
|
|
36
36
|
| `--skip-scan` | Skip the automatic vulnerability scan on install |
|
|
37
|
+
| `--scan-only` | Run the vulnerability scan without installing anything |
|
|
37
38
|
|
|
38
39
|
**Install to a Claude Code project with pre-commit protection:**
|
|
39
40
|
```bash
|
package/index.js
CHANGED
|
@@ -9,14 +9,16 @@ const isLocal = args.includes('--local');
|
|
|
9
9
|
const isClaude = args.includes('--claude');
|
|
10
10
|
const withHooks = args.includes('--with-hooks');
|
|
11
11
|
const skipScan = args.includes('--skip-scan');
|
|
12
|
+
const scanOnly = args.includes('--scan-only');
|
|
12
13
|
|
|
13
14
|
const agentBaseDir = isLocal ? process.cwd() : os.homedir();
|
|
14
15
|
const agentDirName = isClaude ? '.claude' : '.agents';
|
|
15
16
|
const projectDir = process.cwd();
|
|
16
17
|
|
|
17
18
|
const targetSkillDir = path.join(agentBaseDir, agentDirName, 'skills', 'tdd-remediation');
|
|
18
|
-
const targetWorkflowDir =
|
|
19
|
-
|
|
19
|
+
const targetWorkflowDir = isClaude
|
|
20
|
+
? path.join(agentBaseDir, agentDirName, 'commands')
|
|
21
|
+
: path.join(agentBaseDir, agentDirName, 'workflows');
|
|
20
22
|
|
|
21
23
|
// ─── 1. Framework Detection ──────────────────────────────────────────────────
|
|
22
24
|
|
|
@@ -43,7 +45,24 @@ function detectFramework() {
|
|
|
43
45
|
|
|
44
46
|
const framework = detectFramework();
|
|
45
47
|
|
|
46
|
-
// ─── 2.
|
|
48
|
+
// ─── 2. Test Directory Detection ─────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function detectTestBaseDir() {
|
|
51
|
+
// Respect an existing convention before inventing one
|
|
52
|
+
const candidates = ['__tests__', 'tests', 'test', 'spec'];
|
|
53
|
+
for (const dir of candidates) {
|
|
54
|
+
if (fs.existsSync(path.join(projectDir, dir))) return dir;
|
|
55
|
+
}
|
|
56
|
+
// Framework-informed defaults when no directory exists yet
|
|
57
|
+
if (framework === 'pytest') return 'tests';
|
|
58
|
+
if (framework === 'go') return 'test';
|
|
59
|
+
return '__tests__';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const testBaseDir = detectTestBaseDir();
|
|
63
|
+
const targetTestDir = path.join(projectDir, testBaseDir, 'security');
|
|
64
|
+
|
|
65
|
+
// ─── 3. Quick Scan ───────────────────────────────────────────────────────────
|
|
47
66
|
|
|
48
67
|
const VULN_PATTERNS = [
|
|
49
68
|
{ name: 'SQL Injection', severity: 'CRITICAL', pattern: /(`SELECT[^`]*\$\{|"SELECT[^"]*"\s*\+|execute\(f"|cursor\.execute\(.*%s|\.query\(`[^`]*\$\{)/i },
|
|
@@ -111,9 +130,19 @@ function printFindings(findings) {
|
|
|
111
130
|
console.log('\n Run /tdd-audit in your agent to remediate.\n');
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
// ───
|
|
133
|
+
// ─── 4. Scan-only early exit ──────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
if (scanOnly) {
|
|
136
|
+
process.stdout.write('\n🔍 Scanning for vulnerability patterns...');
|
|
137
|
+
const findings = quickScan();
|
|
138
|
+
process.stdout.write('\n');
|
|
139
|
+
printFindings(findings);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── 5. Install Skill Files ───────────────────────────────────────────────────
|
|
115
144
|
|
|
116
|
-
console.log(`\nInstalling TDD Remediation Skill (${isLocal ? 'local' : 'global'}, framework: ${framework})...\n`);
|
|
145
|
+
console.log(`\nInstalling TDD Remediation Skill (${isLocal ? 'local' : 'global'}, framework: ${framework}, test dir: ${testBaseDir}/)...\n`);
|
|
117
146
|
|
|
118
147
|
if (!fs.existsSync(targetSkillDir)) fs.mkdirSync(targetSkillDir, { recursive: true });
|
|
119
148
|
|
|
@@ -123,7 +152,7 @@ for (const item of ['SKILL.md', 'prompts', 'templates']) {
|
|
|
123
152
|
if (fs.existsSync(src)) fs.cpSync(src, dest, { recursive: true });
|
|
124
153
|
}
|
|
125
154
|
|
|
126
|
-
// ───
|
|
155
|
+
// ─── 5. Scaffold Security Test Boilerplate ────────────────────────────────────
|
|
127
156
|
|
|
128
157
|
if (!fs.existsSync(targetTestDir)) {
|
|
129
158
|
fs.mkdirSync(targetTestDir, { recursive: true });
|
|
@@ -147,7 +176,7 @@ if (!fs.existsSync(destTest) && fs.existsSync(srcTest)) {
|
|
|
147
176
|
console.log(`✅ Scaffolded ${path.relative(projectDir, destTest)}`);
|
|
148
177
|
}
|
|
149
178
|
|
|
150
|
-
// ───
|
|
179
|
+
// ─── 6. Install Workflow Shortcode ────────────────────────────────────────────
|
|
151
180
|
|
|
152
181
|
if (!fs.existsSync(targetWorkflowDir)) fs.mkdirSync(targetWorkflowDir, { recursive: true });
|
|
153
182
|
const srcWorkflow = path.join(__dirname, 'workflows', 'tdd-audit.md');
|
|
@@ -157,7 +186,7 @@ if (fs.existsSync(srcWorkflow)) {
|
|
|
157
186
|
console.log(`✅ Installed /tdd-audit workflow shortcode`);
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
// ───
|
|
189
|
+
// ─── 7. Inject test:security into package.json ────────────────────────────────
|
|
161
190
|
|
|
162
191
|
const pkgPath = path.join(projectDir, 'package.json');
|
|
163
192
|
if (framework !== 'pytest' && framework !== 'go' && fs.existsSync(pkgPath)) {
|
|
@@ -165,11 +194,12 @@ if (framework !== 'pytest' && framework !== 'go' && fs.existsSync(pkgPath)) {
|
|
|
165
194
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
166
195
|
if (!pkg.scripts?.['test:security']) {
|
|
167
196
|
pkg.scripts = pkg.scripts || {};
|
|
197
|
+
const secDir = `${testBaseDir}/security`;
|
|
168
198
|
pkg.scripts['test:security'] = {
|
|
169
|
-
jest:
|
|
170
|
-
vitest:
|
|
171
|
-
mocha:
|
|
172
|
-
}[framework] ||
|
|
199
|
+
jest: `jest --testPathPattern=${secDir} --forceExit`,
|
|
200
|
+
vitest: `vitest run ${secDir}`,
|
|
201
|
+
mocha: `mocha '${secDir}/**/*.spec.js'`,
|
|
202
|
+
}[framework] || `jest --testPathPattern=${secDir} --forceExit`;
|
|
173
203
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
174
204
|
console.log(`✅ Added "test:security" script to package.json`);
|
|
175
205
|
} else {
|
|
@@ -180,7 +210,7 @@ if (framework !== 'pytest' && framework !== 'go' && fs.existsSync(pkgPath)) {
|
|
|
180
210
|
}
|
|
181
211
|
}
|
|
182
212
|
|
|
183
|
-
// ───
|
|
213
|
+
// ─── 8. Scaffold CI Workflow ─────────────────────────────────────────────────
|
|
184
214
|
|
|
185
215
|
const ciWorkflowDir = path.join(projectDir, '.github', 'workflows');
|
|
186
216
|
const ciWorkflowPath = path.join(ciWorkflowDir, 'security-tests.yml');
|
|
@@ -203,7 +233,7 @@ if (!fs.existsSync(ciWorkflowPath)) {
|
|
|
203
233
|
console.log(` .github/workflows/security-tests.yml already exists — skipped`);
|
|
204
234
|
}
|
|
205
235
|
|
|
206
|
-
// ───
|
|
236
|
+
// ─── 9. Pre-commit Hook (opt-in) ─────────────────────────────────────────────
|
|
207
237
|
|
|
208
238
|
if (withHooks) {
|
|
209
239
|
const gitDir = path.join(projectDir, '.git');
|
|
@@ -241,7 +271,7 @@ if (withHooks) {
|
|
|
241
271
|
}
|
|
242
272
|
}
|
|
243
273
|
|
|
244
|
-
// ───
|
|
274
|
+
// ─── 10. Quick Scan ──────────────────────────────────────────────────────────
|
|
245
275
|
|
|
246
276
|
if (!skipScan) {
|
|
247
277
|
process.stdout.write('\n🔍 Scanning for vulnerability patterns...');
|
package/package.json
CHANGED
package/prompts/auto-audit.md
CHANGED
|
@@ -101,7 +101,7 @@ Ask the user to confirm the list before beginning remediation. If they say "fix
|
|
|
101
101
|
|
|
102
102
|
For **each** confirmed vulnerability, rigorously apply the RED-GREEN-REFACTOR protocol in order:
|
|
103
103
|
|
|
104
|
-
1. **[RED](./red-phase.md)**: Write the exploit test in `__tests__/security/` and run it to prove the vulnerability exists (test must fail).
|
|
104
|
+
1. **[RED](./red-phase.md)**: Write the exploit test in the project's security test directory (e.g., `tests/security/`, `__tests__/security/`, `test/security/` — wherever the installer scaffolded the boilerplate) and run it to prove the vulnerability exists (test must fail).
|
|
105
105
|
2. **[GREEN](./green-phase.md)**: Apply the targeted patch. Run the exploit test — it must now pass.
|
|
106
106
|
3. **[REFACTOR](./refactor-phase.md)**: Run the full test suite. All tests must be green before moving on.
|
|
107
107
|
|