@lhi/tdd-audit 1.15.0 → 1.18.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/docs/rest-api.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # REST API
2
2
 
3
- `tdd-audit serve` turns the scanner into an authenticated HTTP API built on **Fastify**. Use it to integrate vulnerability scanning and AI remediation into dashboards, CI pipelines, bots, or any tooling that speaks JSON.
3
+ `tdd-audit serve` exposes an authenticated HTTP API built on **Fastify**. Use it to integrate AI-powered security audits into dashboards, CI pipelines, bots, or any tooling that speaks JSON.
4
4
 
5
5
  ---
6
6
 
@@ -59,7 +59,7 @@ By default the rate limiter keys on the **socket IP**, not `X-Forwarded-For`, to
59
59
 
60
60
  ### Path validation
61
61
 
62
- `POST /scan` and `POST /audit` validate that the requested path is inside the server's working directory. The check is normalised with a trailing path separator to prevent sibling-directory prefix bypasses (e.g. `/app-evil` cannot escape via `/app`). Paths outside cwd return `400`.
62
+ `POST /audit` and `POST /audit/ai` validate that the requested path is inside the server's working directory. The check is normalised with a trailing path separator to prevent sibling-directory prefix bypasses (e.g. `/app-evil` cannot escape via `/app`). Paths outside cwd return `400`.
63
63
 
64
64
  ### Security headers
65
65
 
@@ -80,60 +80,117 @@ X-Frame-Options: DENY
80
80
  No auth required. Returns server status and version.
81
81
 
82
82
  ```json
83
- { "status": "ok", "version": "1.13.0" }
83
+ { "status": "ok", "version": "1.17.0" }
84
84
  ```
85
85
 
86
86
  ---
87
87
 
88
- ### `POST /scan`
88
+ ### `POST /audit/ai`
89
89
 
90
- Scan a local path and return structured findings synchronously.
90
+ **The primary endpoint.** Runs a full agentic LLM audit using tool calls (`read_file`, `list_files`, `search_in_files`, `write_file`). Returns immediately with a `jobId`; poll `GET /jobs/:id` or stream `GET /jobs/:id/stream` for results.
91
+
92
+ Provider and API key can be supplied in the request body or pre-configured in `.tdd-audit.json`.
91
93
 
92
94
  **Request**
93
95
  ```json
94
96
  {
95
- "path": ".",
96
- "format": "json"
97
+ "path": ".",
98
+ "provider": "anthropic",
99
+ "apiKey": "sk-ant-...",
100
+ "model": "claude-opus-4-6",
101
+ "baseUrl": null,
102
+ "depth": "tier-2"
97
103
  }
98
104
  ```
99
105
 
100
- | Field | Type | Default | Description |
106
+ | Field | Required | Default | Description |
101
107
  |---|---|---|---|
102
- | `path` | string | cwd | Absolute or relative path to scan. Must be inside server cwd. |
103
- | `format` | `"json"` \| `"sarif"` | `"json"` | Output format |
108
+ | `path` | no | cwd | Path to audit. Must be inside server cwd. |
109
+ | `provider` | yes* | cfg | `anthropic` \| `openai` \| `gemini` \| `ollama` |
110
+ | `apiKey` | yes* | cfg | Provider API key |
111
+ | `model` | no | provider default | Model override |
112
+ | `baseUrl` | no | — | Base URL for OpenAI-compatible services |
113
+ | `depth` | no | `tier-1` | Output depth tier (see below) |
114
+ | `scanOnly` | no | — | Override depth-derived scan mode |
115
+ | `allowWrites` | no | `false` | Override depth-derived write permission |
116
+ | `findings` | no | — | Pre-identified findings array (triggers targeted-apply mode when `depth=tier-4`) |
117
+
118
+ *Required unless configured in `.tdd-audit.json`.
119
+
120
+ **Response — 202 Accepted**
121
+
122
+ ```
123
+ HTTP/1.1 202 Accepted
124
+ Location: /jobs/job_1_...
125
+ Retry-After: 5
126
+ ```
127
+ ```json
128
+ { "jobId": "job_1_1711363200000" }
129
+ ```
130
+
131
+ **Job result envelope** (available via `GET /jobs/:id` after completion)
104
132
 
105
- **Response — JSON**
106
133
  ```json
107
134
  {
108
- "version": "1.13.0",
109
- "summary": { "CRITICAL": 1, "HIGH": 3, "MEDIUM": 1, "LOW": 0 },
110
- "findings": [ ... ],
135
+ "version": "1.16.0",
136
+ "provider": "anthropic",
137
+ "model": "claude-opus-4-6",
138
+ "depth": "tier-2",
139
+ "mode": "scan-only",
140
+ "stack": "Node.js / Express",
141
+ "summary": { "CRITICAL": 1, "HIGH": 2, "MEDIUM": 0, "LOW": 1 },
142
+ "patchesApplied": 0,
143
+ "findings": [ ... ],
111
144
  "likelyFalsePositives": [ ... ],
112
- "exempted": [],
113
- "scannedAt": "2026-03-25T12:00:00.000Z",
114
- "duration": 42
145
+ "remediation": [],
146
+ "scannedAt": "2026-03-26T12:00:00.000Z"
115
147
  }
116
148
  ```
117
149
 
118
- **Response SARIF**
150
+ `patchesApplied` is the billable unit for `tier-4`: the count of `remediation` entries where `status === "fixed"`.
151
+
152
+ ---
153
+
154
+ ### Depth tiers
155
+
156
+ | Tier | Mode | Finding fields added | `allowWrites` |
157
+ |---|---|---|---|
158
+ | `tier-1` | scan-only | `name`, `severity`, `file`, `line`, `snippet` | no |
159
+ | `tier-2` | scan-only | + `risk`, `effort`, `cwe`, `owasp`, `references` | no |
160
+ | `tier-3` | full audit | + `patch` (copy-ready), `testSnippet` | no |
161
+ | `tier-4` | full audit | LLM calls `write_file`; `remediation` array tracks each patch | yes |
162
+
163
+ ---
164
+
165
+ ### Targeted apply (tier-4 + findings)
119
166
 
120
- Returns a SARIF 2.1.0 object ready to upload to GitHub code scanning.
167
+ When `depth=tier-4` and a `findings` array is supplied, the LLM skips the scan phase entirely and applies only the patches you specify. Use this to apply a single finding from a previous tier-3 report:
121
168
 
122
- **Errors**
169
+ ```json
170
+ {
171
+ "provider": "anthropic",
172
+ "apiKey": "sk-ant-...",
173
+ "depth": "tier-4",
174
+ "findings": [
175
+ {
176
+ "name": "SQL Injection",
177
+ "file": "src/db.js",
178
+ "line": 42,
179
+ "patch": "const stmt = db.prepare('SELECT * FROM users WHERE id = ?');\nstmt.run(id);"
180
+ }
181
+ ]
182
+ }
183
+ ```
123
184
 
124
- | Status | Reason |
125
- |---|---|
126
- | 400 | Path traversal attempt, oversized body (> 512 KB), or invalid JSON |
127
- | 401 | Missing or invalid API key |
128
- | 429 | Rate limit exceeded |
185
+ The job mode will be reported as `targeted-apply/tier-4(1)`. Only the listed findings are touched — no full re-scan.
129
186
 
130
187
  ---
131
188
 
132
189
  ### `POST /remediate`
133
190
 
134
- Queue an AI-powered remediation job for a **provided findings list**. Returns immediately with a `jobId`; poll `GET /jobs/:id` (or stream `GET /jobs/:id/stream`) for results.
191
+ Queue an AI-powered remediation job for a **provided findings list**. Returns immediately with a `jobId`.
135
192
 
136
- Use `POST /audit` instead if you want the server to run the scan itself.
193
+ Use `POST /audit/ai` for a fully agentic audit instead.
137
194
 
138
195
  **Request**
139
196
  ```json
@@ -142,34 +199,28 @@ Use `POST /audit` instead if you want the server to run the scan itself.
142
199
  "provider": "anthropic",
143
200
  "apiKey": "sk-ant-...",
144
201
  "model": "claude-opus-4-6",
145
- "baseUrl": null,
146
- "severity": "HIGH"
202
+ "baseUrl": null
147
203
  }
148
204
  ```
149
205
 
150
206
  | Field | Required | Description |
151
207
  |---|---|---|
152
- | `findings` | yes | Array of finding objects from `POST /scan` |
208
+ | `findings` | yes | Array of finding objects |
153
209
  | `provider` | yes | `anthropic` \| `openai` \| `gemini` \| `ollama` |
154
210
  | `apiKey` | yes | Provider API key |
155
- | `model` | no | Defaults per provider (see [AI Remediation](ai-remediation.md)) |
156
- | `baseUrl` | no | Override base URL for any OpenAI-compatible service |
157
- | `severity` | no | Minimum severity to fix. Default: `LOW` (fix all) |
211
+ | `model` | no | Defaults per provider |
212
+ | `baseUrl` | no | Override base URL for OpenAI-compatible services |
158
213
 
159
214
  **Response — 202 Accepted**
160
215
  ```json
161
216
  { "jobId": "job_1_1711363200000" }
162
217
  ```
163
218
 
164
- Job lifecycle: `pending → running → done | error`
165
-
166
219
  ---
167
220
 
168
221
  ### `POST /audit`
169
222
 
170
- Full automated pipeline: **scan + AI remediation in one shot**. No interaction needed. Returns immediately with a `jobId`.
171
-
172
- If no `provider`/`apiKey` are supplied, the server runs the scan only (no remediation) and the job transitions to `done` with just the `findings` array.
223
+ Static scan + AI remediation pipeline in one shot. Runs `quickScan` then passes findings to the remediator. If no `provider`/`apiKey` are supplied, runs the scan only.
173
224
 
174
225
  **Request**
175
226
  ```json
@@ -185,7 +236,7 @@ If no `provider`/`apiKey` are supplied, the server runs the scan only (no remedi
185
236
 
186
237
  | Field | Required | Description |
187
238
  |---|---|---|
188
- | `path` | no | Path to scan. Defaults to cwd. Must be inside server cwd. |
239
+ | `path` | no | Path to scan. Defaults to cwd. |
189
240
  | `provider` | no | If supplied with `apiKey`, AI remediation runs after the scan |
190
241
  | `apiKey` | no | Provider API key |
191
242
  | `model` | no | Defaults per provider |
@@ -196,69 +247,21 @@ If no `provider`/`apiKey` are supplied, the server runs the scan only (no remedi
196
247
 
197
248
  ```
198
249
  HTTP/1.1 202 Accepted
199
- Location: /jobs/job_1_1711363200000
250
+ Location: /jobs/job_1_...
200
251
  Retry-After: 2
201
252
  ```
202
- ```json
203
- { "jobId": "job_1_1711363200000" }
204
- ```
205
253
 
206
254
  Job lifecycle: `pending → scanning → scanned → remediating → done | error`
207
255
 
208
- Poll `GET /jobs/:id` or stream `GET /jobs/:id/stream` for progress.
209
-
210
- **Job object during remediation**
211
- ```json
212
- {
213
- "id": "job_1_...",
214
- "status": "remediating",
215
- "total": 8,
216
- "completed": 3,
217
- "current": "SQL Injection"
218
- }
219
- ```
220
-
221
- **Job object when done**
222
- ```json
223
- {
224
- "id": "job_1_...",
225
- "status": "done",
226
- "createdAt": "...",
227
- "startedAt": "...",
228
- "completedAt": "...",
229
- "findings": [ ... ],
230
- "results": [
231
- {
232
- "finding": { ... },
233
- "status": "remediated",
234
- "exploitTest": { "filename": "__tests__/security/xss.test.js", "content": "..." },
235
- "patch": { "filename": "src/app.js", "diff": "..." },
236
- "refactorChecks": ["npm test", "npm run test:security"]
237
- }
238
- ]
239
- }
240
- ```
241
-
242
256
  ---
243
257
 
244
258
  ### `GET /jobs/:id`
245
259
 
246
- Poll for job status. Works for jobs created by both `POST /remediate` and `POST /audit`.
260
+ Poll for job status. Works for jobs created by `POST /audit/ai`, `POST /remediate`, and `POST /audit`.
247
261
 
248
- **Response — pending / scanning**
262
+ **Response — pending / running**
249
263
  ```json
250
- { "id": "job_1_...", "status": "scanning", "createdAt": "..." }
251
- ```
252
-
253
- **Response — remediating (with progress)**
254
- ```json
255
- {
256
- "id": "job_1_...",
257
- "status": "remediating",
258
- "total": 8,
259
- "completed": 3,
260
- "current": "SQL Injection"
261
- }
264
+ { "id": "job_1_...", "status": "running", "depth": "tier-2", "createdAt": "..." }
262
265
  ```
263
266
 
264
267
  **Response — done**
@@ -269,7 +272,7 @@ Poll for job status. Works for jobs created by both `POST /remediate` and `POST
269
272
  "createdAt": "...",
270
273
  "startedAt": "...",
271
274
  "completedAt": "...",
272
- "results": [ ... ]
275
+ "result": { ... }
273
276
  }
274
277
  ```
275
278
 
@@ -293,18 +296,14 @@ curl -N http://localhost:3000/jobs/job_1_.../stream \
293
296
 
294
297
  **Event format**
295
298
  ```
296
- data: {"id":"job_1_...","status":"scanning","createdAt":"..."}
297
-
298
- data: {"id":"job_1_...","status":"scanned","findings":[...]}
299
-
300
- data: {"id":"job_1_...","status":"remediating","total":8,"completed":1,"current":"SQL Injection"}
299
+ data: {"id":"job_1_...","status":"running","depth":"tier-2","log":"🔍 Exploring..."}
301
300
 
302
- data: {"id":"job_1_...","status":"done","completedAt":"...","results":[...]}
301
+ data: {"id":"job_1_...","status":"done","completedAt":"...","result":{...}}
303
302
  ```
304
303
 
305
- The connection is closed automatically after the terminal state (`done` / `error`). If you connect to an already-completed job, the server pushes the current state and closes immediately.
304
+ The connection closes automatically after the terminal state. Connecting to an already-completed job pushes the current state and closes immediately.
306
305
 
307
- **Node.js example using EventSource**
306
+ **Node.js example**
308
307
  ```javascript
309
308
  const es = new EventSource(
310
309
  'http://localhost:3000/jobs/job_1_.../stream',
@@ -312,7 +311,7 @@ const es = new EventSource(
312
311
  );
313
312
  es.onmessage = (e) => {
314
313
  const job = JSON.parse(e.data);
315
- if (job.status === 'done') { console.log(job.results); es.close(); }
314
+ if (job.status === 'done') { console.log(job.result); es.close(); }
316
315
  if (job.status === 'error') { console.error(job.error); es.close(); }
317
316
  };
318
317
  ```
@@ -321,28 +320,19 @@ es.onmessage = (e) => {
321
320
 
322
321
  ## Full workflow examples
323
322
 
324
- ### curl — scan only
323
+ ### curl — tier-2 audit with polling
325
324
 
326
325
  ```bash
327
326
  npx @lhi/tdd-audit serve --port 3000 --api-key mysecret &
328
327
 
329
- curl -s -X POST http://localhost:3000/scan \
330
- -H "Authorization: Bearer mysecret" \
331
- -H "Content-Type: application/json" \
332
- -d '{"path": "."}' | jq '.summary'
333
- ```
334
-
335
- ### curl — full pipeline with polling
336
-
337
- ```bash
338
- # Kick off audit
339
- JOB=$(curl -s -X POST http://localhost:3000/audit \
328
+ # Start the audit
329
+ JOB=$(curl -s -X POST http://localhost:3000/audit/ai \
340
330
  -H "Authorization: Bearer mysecret" \
341
331
  -H "Content-Type: application/json" \
342
332
  -d '{
343
- "path": ".",
344
333
  "provider": "anthropic",
345
- "apiKey": "sk-ant-..."
334
+ "apiKey": "sk-ant-...",
335
+ "depth": "tier-2"
346
336
  }' | jq -r '.jobId')
347
337
 
348
338
  # Poll until done
@@ -351,30 +341,53 @@ while true; do
351
341
  -H "Authorization: Bearer mysecret" | jq -r '.status')
352
342
  echo "Status: $STATUS"
353
343
  [ "$STATUS" = "done" ] || [ "$STATUS" = "error" ] && break
354
- sleep 2
344
+ sleep 3
355
345
  done
346
+
347
+ # Print findings summary
348
+ curl -s http://localhost:3000/jobs/$JOB \
349
+ -H "Authorization: Bearer mysecret" | jq '.result.summary'
356
350
  ```
357
351
 
358
- ### curl — SARIF output for GitHub code scanning
352
+ ### curl — tier-3 report then targeted apply
359
353
 
360
354
  ```bash
361
- curl -s -X POST http://localhost:3000/scan \
355
+ # Step 1: get copy-ready patches (no writes)
356
+ JOB=$(curl -s -X POST http://localhost:3000/audit/ai \
362
357
  -H "Authorization: Bearer mysecret" \
363
358
  -H "Content-Type: application/json" \
364
- -d '{"path": ".", "format": "sarif"}' > results.sarif
359
+ -d '{"provider":"anthropic","apiKey":"sk-ant-...","depth":"tier-3"}' \
360
+ | jq -r '.jobId')
361
+
362
+ # (wait for done)
363
+
364
+ # Step 2: apply one specific patch
365
+ FINDING=$(curl -s http://localhost:3000/jobs/$JOB \
366
+ -H "Authorization: Bearer mysecret" \
367
+ | jq '.result.findings[0]')
368
+
369
+ curl -s -X POST http://localhost:3000/audit/ai \
370
+ -H "Authorization: Bearer mysecret" \
371
+ -H "Content-Type: application/json" \
372
+ -d "{
373
+ \"provider\": \"anthropic\",
374
+ \"apiKey\": \"sk-ant-...\",
375
+ \"depth\": \"tier-4\",
376
+ \"findings\": [$FINDING]
377
+ }" | jq '.jobId'
365
378
  ```
366
379
 
367
- ### Node.jsscan
380
+ ### curlSARIF output for GitHub code scanning
368
381
 
369
- ```javascript
370
- const res = await fetch('http://localhost:3000/scan', {
371
- method: 'POST',
372
- headers: {
373
- 'Authorization': 'Bearer mysecret',
374
- 'Content-Type': 'application/json',
375
- },
376
- body: JSON.stringify({ path: '/path/to/project' }),
377
- });
378
- const { findings, summary } = await res.json();
379
- console.log(`CRITICAL: ${summary.CRITICAL} HIGH: ${summary.HIGH}`);
382
+ ```bash
383
+ JOB=$(curl -s -X POST http://localhost:3000/audit/ai \
384
+ -H "Authorization: Bearer mysecret" \
385
+ -H "Content-Type: application/json" \
386
+ -d '{"provider":"anthropic","apiKey":"sk-ant-...","depth":"tier-2","format":"sarif"}' \
387
+ | jq -r '.jobId')
388
+
389
+ # (wait for done)
390
+
391
+ curl -s http://localhost:3000/jobs/$JOB \
392
+ -H "Authorization: Bearer mysecret" | jq '.result' > results.sarif
380
393
  ```
package/docs/scanner.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # Scanner Architecture
2
2
 
3
- `lib/scanner.js` is the core engine behind `npx @lhi/tdd-audit --scan` and the auto-audit skill. It is a pure Node.js module with no runtime dependencies — only `fs` and `path`.
3
+ `lib/scanner.js` provides the static vulnerability pattern engine used internally by the tdd-audit skill and the `POST /audit` pipeline. It is a pure Node.js module with no runtime dependencies — only `fs` and `path`.
4
+
5
+ > The scanner is an internal pre-processing component, not a standalone product. Customer-facing output is produced by the LLM audit (`--ai` / `POST /audit/ai`), which uses the scanner's signal as one input alongside its own tool-use exploration.
4
6
 
5
7
  ---
6
8
 
7
- ## Entry points
9
+ ## Exports
8
10
 
9
11
  | Export | Purpose |
10
12
  |---|---|
@@ -64,7 +66,7 @@ Same skip rules, yields `.md` files only. Used by `scanPromptFiles`.
64
66
 
65
67
  `.js` `.ts` `.jsx` `.tsx` `.mjs` `.py` `.go` `.dart` `.yml` `.yaml`
66
68
 
67
- JSON and XML files are not walked by the code scanner. `package.json` is handled by `scanPackageJson()` and `.env*` files by `scanEnvFiles()` — both run as separate targeted checks. CI workflow files (`.yml`/`.yaml`) **are** now scanned by `walkFiles()` for GitHub Actions expression injection and similar patterns.
69
+ JSON and XML files are not walked by the code scanner. `package.json` is handled by `scanPackageJson()` and `.env*` files by `scanEnvFiles()` — both run as separate targeted checks. CI workflow files (`.yml`/`.yaml`) **are** scanned by `walkFiles()` for GitHub Actions expression injection and similar patterns.
68
70
 
69
71
  ---
70
72