@lhi/tdd-audit 1.5.0 → 1.8.1
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/lib/scanner.js +71 -30
- package/package.json +1 -1
- package/workflows/tdd-audit.md +6 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @lhi/tdd-audit
|
|
2
2
|
|
|
3
|
-
Security skill installer for **Claude Code, Gemini CLI, Cursor, Codex, and OpenCode**. Patches vulnerabilities using a Red-Green-Refactor exploit-test protocol — you prove the hole exists, apply the fix, and prove it's closed.
|
|
3
|
+
> **v1.8.0** — Security skill installer for **Claude Code, Gemini CLI, Cursor, Codex, and OpenCode**. Patches vulnerabilities using a Red-Green-Refactor exploit-test protocol — you prove the hole exists, apply the fix, and prove it's closed.
|
|
4
4
|
|
|
5
5
|
## What happens on install
|
|
6
6
|
|
|
7
7
|
Running the installer does five things immediately:
|
|
8
8
|
|
|
9
|
-
1. **Scans your codebase** for
|
|
9
|
+
1. **Scans your codebase** for 34 vulnerability patterns across OWASP Top 10, mobile, agentic AI, and prompt/skill files — prints a severity-ranked findings report to stdout
|
|
10
10
|
2. **Scaffolds `__tests__/security/`** with a framework-matched boilerplate exploit test
|
|
11
11
|
3. **Adds `test:security`** to your `package.json` scripts (Node.js projects)
|
|
12
12
|
4. **Creates `.github/workflows/security-tests.yml`** so the CI gate exists from day one
|
|
@@ -31,25 +31,25 @@ node index.js
|
|
|
31
31
|
| Claude Code | `npx @lhi/tdd-audit --local --claude` |
|
|
32
32
|
| Gemini CLI / Codex / OpenCode | `npx @lhi/tdd-audit --local` |
|
|
33
33
|
| With pre-commit hook | add `--with-hooks` |
|
|
34
|
-
| Scan only (no install) | `npx @lhi/tdd-audit --scan
|
|
34
|
+
| Scan only (no install) | `npx @lhi/tdd-audit --scan` |
|
|
35
35
|
|
|
36
36
|
### All flags
|
|
37
37
|
|
|
38
38
|
| Flag | Description |
|
|
39
39
|
|---|---|
|
|
40
|
-
| `--local` | Install skill files
|
|
40
|
+
| `--local` | Install skill files into the current project instead of `~` |
|
|
41
41
|
| `--claude` | Use `.claude/` instead of `.agents/` as the skill directory |
|
|
42
42
|
| `--with-hooks` | Install a pre-commit hook that blocks commits if security tests fail |
|
|
43
43
|
| `--skip-scan` | Skip the automatic vulnerability scan on install |
|
|
44
|
-
| `--scan-only` | Run the vulnerability scan without installing anything |
|
|
44
|
+
| `--scan` / `--scan-only` | Run the vulnerability scan without installing anything |
|
|
45
45
|
|
|
46
|
-
### Framework
|
|
46
|
+
### Framework detection
|
|
47
47
|
|
|
48
48
|
The installer automatically detects your project's test framework and scaffolds the right boilerplate:
|
|
49
49
|
|
|
50
50
|
| Detected | Boilerplate | `test:security` command |
|
|
51
51
|
|---|---|---|
|
|
52
|
-
| `jest` / `supertest` | `sample.exploit.test.js` | `jest --
|
|
52
|
+
| `jest` / `supertest` | `sample.exploit.test.js` | `jest --testPathPatterns=__tests__/security` |
|
|
53
53
|
| `vitest` | `sample.exploit.test.vitest.js` | `vitest run __tests__/security` |
|
|
54
54
|
| `mocha` | `sample.exploit.test.js` | `mocha '__tests__/security/**/*.spec.js'` |
|
|
55
55
|
| `pytest.ini` / `pyproject.toml` | `sample.exploit.test.pytest.py` | `pytest tests/security/ -v` |
|
|
@@ -65,33 +65,46 @@ Once installed, trigger the autonomous audit in your agent:
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
The agent will:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
|
|
69
|
+
1. Detect your tech stack and scope the scan to relevant patterns only
|
|
70
|
+
2. Scan the codebase and present a severity-ranked findings report (CRITICAL / HIGH / MEDIUM / LOW)
|
|
71
|
+
3. **Wait for your confirmation** before making any changes
|
|
72
|
+
4. For each confirmed vulnerability, apply the full Red-Green-Refactor loop:
|
|
71
73
|
- **Red** — write an exploit test that fails, proving the vulnerability exists
|
|
72
74
|
- **Green** — apply the targeted patch, making the test pass
|
|
73
75
|
- **Refactor** — run the full suite to confirm no regressions
|
|
74
|
-
|
|
76
|
+
5. Apply proactive hardening controls (security headers, rate limiting, `npm audit`, secret history scan)
|
|
77
|
+
6. Deliver a final Remediation Summary table
|
|
75
78
|
|
|
76
79
|
The agent works one vulnerability at a time and does not advance until the current one is fully proven closed.
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
Pass `--scan` in your prompt to get the Audit Report only, without any code changes.
|
|
82
|
+
|
|
83
|
+
## Vulnerability scanner
|
|
79
84
|
|
|
80
|
-
The built-in scanner catches
|
|
85
|
+
The built-in scanner catches **34 patterns** across OWASP Top 10, mobile, agentic AI, and prompt/skill files:
|
|
81
86
|
|
|
82
87
|
| Category | Patterns |
|
|
83
88
|
|---|---|
|
|
84
|
-
| Injection | SQL Injection, Command Injection, NoSQL Injection, Template Injection
|
|
85
|
-
| Broken Auth | JWT
|
|
89
|
+
| Injection | SQL Injection, Command Injection, NoSQL Injection, Template Injection |
|
|
90
|
+
| Broken Auth | JWT Alg None, Broken Auth, Timing-Unsafe Comparison, Hardcoded Secret, Secret Fallback |
|
|
86
91
|
| XSS / Output | XSS, eval() Injection, Open Redirect |
|
|
87
92
|
| Crypto | Weak Crypto (MD5/SHA1), Insecure Random, TLS Bypass |
|
|
88
93
|
| Server-side | SSRF, Path Traversal, XXE, Insecure Deserialization |
|
|
89
94
|
| Assignment | Mass Assignment, Prototype Pollution |
|
|
90
95
|
| Mobile | Sensitive Storage, WebView JS Bridge, Deep Link Injection, Android Debuggable |
|
|
91
|
-
| Config | CORS Wildcard, Cleartext Traffic, Config Secrets |
|
|
92
|
-
|
|
|
96
|
+
| Config / Infra | CORS Wildcard, Cleartext Traffic, Config Secrets, ReDoS |
|
|
97
|
+
| Agentic / Prompt | Deprecated CSRF Package (`csurf`), Unpinned npx MCP Server, Cleartext URL in Prompt |
|
|
98
|
+
|
|
99
|
+
### Scanner behaviour
|
|
93
100
|
|
|
94
|
-
|
|
101
|
+
- **Test files are flagged but labelled** — findings in `__tests__/`, `tests/`, `spec/`, or `*.test.*` files are shown with a `[test file]` badge. Patterns that mark `skipInTests: true` (e.g. Hardcoded Secret, Sensitive Log, Cleartext Traffic) are further tagged `likelyFalsePositive` and separated at the bottom of the report.
|
|
102
|
+
- **Prompt/skill files get their own scan** — `.md` files inside `prompts/`, `skills/`, `.claude/`, `workflows/`, plus `CLAUDE.md` and `SKILL.md`, are scanned for prompt-specific anti-patterns. Matches inside backtick code spans are suppressed to avoid noise from documentation examples.
|
|
103
|
+
- **`audit_status: safe` exemption** — any prompt file with `audit_status: safe` in its YAML frontmatter is skipped and listed separately so you can verify exemptions are intentional.
|
|
104
|
+
- **Binary and oversized files skipped** — files larger than 512 KB or containing null bytes are skipped to prevent OOM.
|
|
105
|
+
- **Symlinks skipped** — symlinks are never followed, preventing directory-escape on M-series Macs and shared filesystems.
|
|
106
|
+
|
|
107
|
+
## Running security tests
|
|
95
108
|
|
|
96
109
|
```bash
|
|
97
110
|
# Node.js
|
|
@@ -102,20 +115,30 @@ pytest tests/security/ -v
|
|
|
102
115
|
|
|
103
116
|
# Go
|
|
104
117
|
go test ./security/... -v
|
|
118
|
+
|
|
119
|
+
# Flutter
|
|
120
|
+
flutter test test/security/
|
|
105
121
|
```
|
|
106
122
|
|
|
107
123
|
## CI/CD
|
|
108
124
|
|
|
109
|
-
The installer creates `.github/workflows
|
|
125
|
+
The installer creates framework-matched workflow files under `.github/workflows/`. Both `security-tests.yml` and `ci.yml` include:
|
|
126
|
+
|
|
127
|
+
- SHA-pinned `uses:` references on every action (supply chain hardening)
|
|
128
|
+
- `npm audit --audit-level=high` (or equivalent) to catch vulnerable dependencies
|
|
129
|
+
- The security exploit test suite on every push and pull request
|
|
110
130
|
|
|
111
|
-
To add
|
|
131
|
+
To add the security gate to an existing pipeline manually:
|
|
112
132
|
|
|
113
133
|
```yaml
|
|
134
|
+
- name: Dependency audit
|
|
135
|
+
run: npm audit --audit-level=high
|
|
136
|
+
|
|
114
137
|
- name: Run security exploit tests
|
|
115
|
-
run: npm run test:security # or pytest tests/security/,
|
|
138
|
+
run: npm run test:security # or pytest tests/security/, flutter test test/security/
|
|
116
139
|
```
|
|
117
140
|
|
|
118
|
-
## Pre-commit
|
|
141
|
+
## Pre-commit hook
|
|
119
142
|
|
|
120
143
|
The `--with-hooks` flag appends a security gate to `.git/hooks/pre-commit`. Commits are blocked if any exploit test fails:
|
|
121
144
|
|
|
@@ -123,7 +146,37 @@ The `--with-hooks` flag appends a security gate to `.git/hooks/pre-commit`. Comm
|
|
|
123
146
|
❌ Security tests failed. Commit blocked.
|
|
124
147
|
```
|
|
125
148
|
|
|
126
|
-
The hook is non-destructive — it appends to
|
|
149
|
+
The hook is non-destructive — it appends to existing hook content rather than overwriting it.
|
|
150
|
+
|
|
151
|
+
## Agentic AI security (ASI01–ASI10)
|
|
152
|
+
|
|
153
|
+
When the project contains AI agent code, MCP configurations, or `CLAUDE.md` files, the scanner also checks for agentic-specific vulnerabilities:
|
|
154
|
+
|
|
155
|
+
| ID | Vulnerability | Risk |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| ASI01 | Prompt injection via tool output | Malicious content in web/file reads hijacks agent behaviour |
|
|
158
|
+
| ASI02 | CLAUDE.md / instructions file injection | Attacker-controlled system prompts override agent identity |
|
|
159
|
+
| ASI03 | MCP server supply chain (unpinned `npx`) | Compromised package version exfiltrates secrets |
|
|
160
|
+
| ASI04 | Excessive tool permissions | Agent can write files or run shell when only read is needed |
|
|
161
|
+
| ASI05 | Secrets in tool call arguments | Tokens/passwords logged by external tools |
|
|
162
|
+
| ASI06 | Unvalidated agent action execution | Agent runs irreversible actions without user confirmation |
|
|
163
|
+
| ASI07 | Insecure direct agent communication | Sub-agent messages trusted without verification |
|
|
164
|
+
| ASI08 | GitHub Actions command injection | `github.event.*` interpolated directly into `run:` steps |
|
|
165
|
+
| ASI09 | Unpinned GitHub Actions (supply chain) | Mutable `@v4` / `@main` tags can be hijacked |
|
|
166
|
+
| ASI10 | Secrets in workflow environment | Secrets printed to logs or embedded in curl URLs |
|
|
167
|
+
|
|
168
|
+
See [`docs/agentic-ai-security.md`](docs/agentic-ai-security.md) for grep patterns, examples, and fixes.
|
|
169
|
+
|
|
170
|
+
## Documentation
|
|
171
|
+
|
|
172
|
+
| File | Contents |
|
|
173
|
+
|---|---|
|
|
174
|
+
| [`docs/scanner.md`](docs/scanner.md) | How the scanner works — architecture, detection logic, false-positive handling |
|
|
175
|
+
| [`docs/vulnerability-patterns.md`](docs/vulnerability-patterns.md) | All 34 patterns with descriptions, grep signatures, and fix pointers |
|
|
176
|
+
| [`docs/tdd-protocol.md`](docs/tdd-protocol.md) | The Red-Green-Refactor protocol in full, with framework templates |
|
|
177
|
+
| [`docs/agentic-ai-security.md`](docs/agentic-ai-security.md) | ASI01–ASI10 agentic AI vulnerability reference |
|
|
178
|
+
| [`docs/hardening.md`](docs/hardening.md) | Phase 4 proactive hardening controls |
|
|
179
|
+
| [`docs/ci-cd.md`](docs/ci-cd.md) | CI/CD integration guide for all supported stacks |
|
|
127
180
|
|
|
128
181
|
## License
|
|
129
182
|
|
package/SKILL.md
CHANGED
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.1",
|
|
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": {
|
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
|
|