@thedecipherist/mdd 1.7.1 → 1.8.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.
@@ -0,0 +1,303 @@
1
+ ## SECURITY RULES MODE — `/mdd security-rules`
2
+
3
+ Triggered when arguments start with `security-rules`. Also triggered automatically by AUDIT MODE when `$MDD_SECURITY_SCAN` is `true` (set via `securityScan: true` in `.mdd/settings.json`).
4
+
5
+ This mode scans the project's declared stack for known vulnerabilities using free, locally-available tools, then compares each finding against existing MDD stack rule files. Any vulnerability pattern not already covered by an existing rule becomes a new rule, appended to the relevant `mdd-rules-{stack}.md` file. The goal is to keep rule files current without manual maintenance.
6
+
7
+ **No API keys or paid accounts required.** All scanners used are free-tier or open tools.
8
+
9
+ **This mode only generates audit rules.** It does not patch dependencies, modify source code, or install anything.
10
+
11
+ ---
12
+
13
+ ### Phase SS0 — Read Current Rules
14
+
15
+ ```bash
16
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS0" start "$MDD_STACK"
17
+ ```
18
+
19
+ Load all existing MDD stack rule files so the gap analysis in Phase SS2 has something to compare against.
20
+
21
+ **Step 1 — Locate rule files:**
22
+
23
+ For each entry in `$MDD_STACK`, check whether `.mdd/mdd-rules-{entry}.md` exists. Build two lists:
24
+
25
+ - `$RULE_FILES_FOUND` — entries that have a rule file
26
+ - `$RULE_FILES_MISSING` — entries with no rule file yet (a new file will be created in Phase SS3 if needed)
27
+
28
+ **Step 2 — Extract existing rule descriptions:**
29
+
30
+ For each file in `$RULE_FILES_FOUND`, read every bullet line that matches the MDD rule format:
31
+ ```
32
+ - [P{N}] {description}
33
+ ```
34
+
35
+ Extract the description text from each rule into a comparison set. Also extract any `Reference: CVE-XXXX-XXXXX` tokens present — these are the dedup keys used in Phase SS2.
36
+
37
+ Store the full set in memory as `$EXISTING_RULES` — a list of objects:
38
+ ```
39
+ {
40
+ stack_entry: "express",
41
+ priority: "P2",
42
+ description: "...",
43
+ cve_refs: ["CVE-2024-12345"] // empty array if no Reference tag
44
+ }
45
+ ```
46
+
47
+ If no rule files exist at all, `$EXISTING_RULES` is empty and every finding in Phase SS1 will be treated as a gap.
48
+
49
+ ```bash
50
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS0" end "$MDD_STACK"
51
+ ```
52
+
53
+ ---
54
+
55
+ ### Phase SS1 — Scan
56
+
57
+ ```bash
58
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS1" start "$MDD_STACK"
59
+ ```
60
+
61
+ Run vulnerability scanners against the project. Scanners are tried in priority order. Each failure is a one-line warning — never a halt.
62
+
63
+ **Scanner priority order:**
64
+
65
+ 1. `npm audit` — Node/JS projects. Available if `package.json` or `package-lock.json` exists in the project root.
66
+ 2. `osv-scanner` — multi-language (Node, Go, Python, PHP, Ruby). Available if installed (`which osv-scanner`).
67
+ 3. `snyk` — multi-language, free CLI tier. Available if installed (`which snyk`).
68
+
69
+ **Running each scanner:**
70
+
71
+ For `npm audit`:
72
+ ```bash
73
+ npm audit --json 2>/dev/null
74
+ ```
75
+ Parse the JSON output. For each finding extract: `name` (package), `severity`, `via[].title` or `via[].url` for the vulnerability description, `via[].cve` or the advisory ID as the CVE ref, and the `fixAvailable` field.
76
+
77
+ For `osv-scanner`:
78
+ ```bash
79
+ osv-scanner --format json . 2>/dev/null
80
+ ```
81
+ Parse the JSON output. For each result entry extract: `packages[].package.name`, `packages[].package.ecosystem`, `vulnerabilities[].id` (the CVE or OSV ID), `vulnerabilities[].summary`, `vulnerabilities[].database_specific.severity`.
82
+
83
+ For `snyk`:
84
+ ```bash
85
+ snyk test --json 2>/dev/null
86
+ ```
87
+ Parse the JSON output. For each vulnerability in `vulnerabilities[]` extract: `packageName`, `severity`, `title`, `identifiers.CVE[0]` as the CVE ref, and `description`.
88
+
89
+ **Failure handling for each scanner:**
90
+
91
+ If a scanner is not installed, outputs non-JSON, exits with a parse error, or times out after 30 seconds:
92
+ - Emit one line: `⚠ {scanner-name} skipped: {reason}`
93
+ - Continue with remaining scanners
94
+
95
+ If zero scanners are applicable to the declared stack (e.g., Go-only project with no `osv-scanner` installed), emit:
96
+ ```
97
+ ⚠ No scanners available for stack: {$MDD_STACK}
98
+ Install osv-scanner (https://google.github.io/osv-scanner/) for multi-language support.
99
+ ```
100
+ Then skip to Phase SS4 with zero findings.
101
+
102
+ **Aggregate all findings** from all successful scanners into `$SCAN_FINDINGS`:
103
+ ```
104
+ {
105
+ scanner: "npm-audit",
106
+ package: "express",
107
+ ecosystem: "npm",
108
+ severity: "high", // critical | high | medium | low
109
+ cve_id: "CVE-2024-29041",
110
+ title: "Open redirect in express",
111
+ description: "...",
112
+ attack_vector: "open redirect via unvalidated Location header"
113
+ }
114
+ ```
115
+
116
+ Deduplicate by `cve_id` — if two scanners report the same CVE, keep one entry (prefer the one with the richer description).
117
+
118
+ ```bash
119
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS1" end "$MDD_STACK"
120
+ ```
121
+
122
+ ---
123
+
124
+ ### Phase SS2 — Gap Analysis
125
+
126
+ ```bash
127
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS2" start "$MDD_STACK"
128
+ ```
129
+
130
+ For each finding in `$SCAN_FINDINGS`, determine whether the vulnerability pattern is already covered by an existing rule.
131
+
132
+ **How the gap check works:**
133
+
134
+ This is a semantic comparison, not a string match. A finding is covered if an existing rule already addresses the same vulnerability *class* — even if the rule was written in different words or references a different CVE.
135
+
136
+ Apply the following two-step check in order:
137
+
138
+ **Step 1 — CVE dedup (exact match):**
139
+ If any rule in `$EXISTING_RULES` contains `Reference: {cve_id}` where `cve_id` matches the finding's CVE ID exactly — the finding is already covered. Skip it. This prevents regenerating rules that were added by a previous security scan.
140
+
141
+ **Step 2 — Semantic coverage check:**
142
+ Read the finding's `attack_vector` and `title`. Then ask: does any existing rule in `$EXISTING_RULES` address the same vulnerability class?
143
+
144
+ Examples of semantic coverage (do NOT regenerate):
145
+ - Finding: "prototype pollution via `req.query`" — covered by any rule mentioning prototype pollution on user-supplied input
146
+ - Finding: "ReDoS via malformed email input" — covered by any rule about regex denial-of-service or unbounded regex on user input
147
+ - Finding: "path traversal in static file serving" — covered by any rule requiring path confinement or jailRoot validation
148
+
149
+ Examples where a new rule IS needed (not semantically covered):
150
+ - Finding: "open redirect via unvalidated Location header" — NOT covered by a rule about SSRF (different class)
151
+ - Finding: "timing attack on HMAC comparison" — NOT covered by a generic input validation rule
152
+
153
+ If the finding passes both checks with no coverage match, it is a **gap** — add it to `$RULE_GAPS`.
154
+
155
+ **Classify each gap by stack entry:**
156
+
157
+ Map the gap's `package` or `ecosystem` back to the most relevant stack entry in `$MDD_STACK`. If no entry matches directly, use the ecosystem name as a fallback stack label (e.g., `npm`, `pypi`).
158
+
159
+ ```bash
160
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS2" end "$MDD_STACK"
161
+ ```
162
+
163
+ ---
164
+
165
+ ### Phase SS3 — Rule Generation
166
+
167
+ ```bash
168
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS3" start "$MDD_STACK"
169
+ ```
170
+
171
+ For each gap in `$RULE_GAPS`, generate a new MDD audit rule and append it to the relevant rule file.
172
+
173
+ **Severity mapping (CVSS → MDD priority):**
174
+
175
+ | CVSS severity | MDD priority |
176
+ |---|---|
177
+ | critical | P2 |
178
+ | high | P2 |
179
+ | medium | P3 |
180
+ | low | P4 |
181
+
182
+ P1 is reserved for MDD's own hardcoded security invariants and is **never auto-generated** by this mode.
183
+
184
+ **Rule format:**
185
+
186
+ ```markdown
187
+ - [P{N}] {attack_vector_description} - {what to check for}. Reference: {CVE_ID}.
188
+ ```
189
+
190
+ **Writing a good generated rule:**
191
+
192
+ The rule must target the vulnerability *class*, not the specific package version or CVE instance. A good rule catches the pattern in new code and in dependency updates before they appear in future vulnerability databases.
193
+
194
+ Guidelines:
195
+ - Describe the attack vector in general terms (e.g., "unvalidated redirect destination", "prototype pollution via query parameters", "timing-unsafe string comparison for secrets")
196
+ - State what the audit should check for — a concrete, actionable thing to look at in code review
197
+ - Include the CVE ID as a `Reference:` tag so the dedup check in future scans can identify it
198
+ - Write the rule in the same imperative style as existing rules in the file
199
+
200
+ Good example:
201
+ ```markdown
202
+ - [P2] Unvalidated redirect destination allows open redirect attacks - check that redirect targets are validated against an allowlist or are relative paths only. Reference: CVE-2024-29041.
203
+ ```
204
+
205
+ Bad example (too CVE-specific, won't catch future variants):
206
+ ```markdown
207
+ - [P2] express 4.x open redirect via res.location() - upgrade to 4.19.2. Reference: CVE-2024-29041.
208
+ ```
209
+
210
+ **Appending to rule files:**
211
+
212
+ For each stack entry with new rules:
213
+ 1. Check whether `.mdd/mdd-rules-{stack-entry}.md` exists.
214
+ - If yes: append the new rules to the end of the file.
215
+ - If no: create the file with a minimal header and the new rules:
216
+ ```markdown
217
+ # MDD Rules — {stack-entry}
218
+
219
+ Stack-specific audit rules for {stack-entry}. Auto-generated from vulnerability scan results.
220
+
221
+ ```
222
+ 2. Append each new rule as a bullet line under a `## Security (auto-generated)` section header.
223
+ - If that section already exists in the file: append to it.
224
+ - If it does not exist yet: add it before appending rules.
225
+
226
+ Track how many rules were added per file for the Phase SS4 summary.
227
+
228
+ ```bash
229
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS3" end "$MDD_STACK"
230
+ ```
231
+
232
+ ---
233
+
234
+ ### Phase SS4 — Report
235
+
236
+ ```bash
237
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS4" start "$MDD_STACK"
238
+ ```
239
+
240
+ Write the scan summary to `.mdd/audits/security-scan-{YYYY-MM-DD}.md` (date from current date).
241
+
242
+ **Summary file format:**
243
+
244
+ ```markdown
245
+ # Security Scan — {YYYY-MM-DD}
246
+
247
+ ## Sources
248
+
249
+ | Scanner | Status | Findings |
250
+ |---------|--------|----------|
251
+ | npm audit | ok | {N} |
252
+ | osv-scanner | skipped: not installed | - |
253
+ | snyk | ok | {N} |
254
+
255
+ ## Results
256
+
257
+ Findings reviewed: {N}
258
+ Already covered: {N}
259
+ New gaps found: {N}
260
+ New rules generated: {N}
261
+
262
+ ### Rules Added
263
+
264
+ - mdd-rules-{stack-entry}.md: +{N} rules
265
+ - mdd-rules-{stack-entry}.md: +{N} rules
266
+
267
+ ### New Rules (full text)
268
+
269
+ {list each generated rule with its target file}
270
+ ```
271
+
272
+ If zero findings were returned across all scanners (clean bill of health):
273
+
274
+ ```markdown
275
+ # Security Scan — {YYYY-MM-DD}
276
+
277
+ All scanners returned zero findings. No new rules generated.
278
+
279
+ Scanners run: {list}
280
+ Stack checked: {$MDD_STACK}
281
+ ```
282
+
283
+ **Present to the user:**
284
+
285
+ ```
286
+ Security scan complete — {YYYY-MM-DD}
287
+
288
+ Scanners: {list of scanners that ran} ({N} skipped)
289
+ Findings: {N} total, {N} new gaps
290
+ Rules added: {N} ({list of files updated})
291
+
292
+ Full report: .mdd/audits/security-scan-{YYYY-MM-DD}.md
293
+ ```
294
+
295
+ If running as a sub-task of AUDIT MODE (triggered via `$MDD_SECURITY_SCAN`), suppress the user-facing output above — just write the file and return. AUDIT MODE's own output will surface the result.
296
+
297
+ ```bash
298
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "Phase SS4" end "$MDD_STACK"
299
+ ```
300
+
301
+ ```bash
302
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh "mdd-security-rules" "-" "complete" "$MDD_STACK"
303
+ ```
package/commands/mdd.md CHANGED
@@ -98,6 +98,85 @@ Read CLAUDE.md for the full rulebook. Key rules:
98
98
 
99
99
  This bootstrap runs for **all modes** — build, audit, scan, update, and every other mode — so no mode ever fails due to a missing `.mdd/` structure.
100
100
 
101
+ ## Step 0c — Settings Bootstrap (silent, automatic)
102
+
103
+ After the directory structure is confirmed, load project settings. This also runs for every mode and never prompts the user.
104
+
105
+ **Read or create `.mdd/settings.json`:**
106
+
107
+ If the file does not exist, create it with this default and report one line:
108
+
109
+ ```json
110
+ {
111
+ "autoDiscovery": true,
112
+ "stack": {
113
+ "language": [],
114
+ "runtime": [],
115
+ "frameworks": [],
116
+ "orm": [],
117
+ "auth": []
118
+ },
119
+ "overrides": {},
120
+ "phaseLogging": true,
121
+ "securityScan": false
122
+ }
123
+ ```
124
+
125
+ ```
126
+ 📋 MDD settings initialised: .mdd/settings.json
127
+ ```
128
+
129
+ If the file exists but cannot be read or is not valid JSON: emit one warning (`⚠ settings.json unreadable — running without stack rules`) and proceed with all defaults. Never halt.
130
+
131
+ **Set session variables from settings:**
132
+
133
+ - `$MDD_PHASE_LOGGING` = `settings.phaseLogging` (default: `true`)
134
+ - `$MDD_SECURITY_SCAN` = `settings.securityScan` (default: `false`)
135
+
136
+ **If `autoDiscovery: true`, run stack detection** — check manifest files in the project root:
137
+
138
+ | File | What to detect |
139
+ |------|----------------|
140
+ | `package.json` | language (`typescript` if typescript in deps, else `javascript`), runtime (`node`), plus frameworks/orm/auth from dep names below |
141
+ | `go.mod` | language: `go` |
142
+ | `pyproject.toml` or `requirements.txt` | language: `python` |
143
+ | `composer.json` | language: `php` |
144
+
145
+ For `package.json`, scan both `dependencies` and `devDependencies` for these known packages:
146
+
147
+ | Package(s) | Category | Stack value |
148
+ |-----------|----------|-------------|
149
+ | `typescript` | language | `typescript` |
150
+ | `express` | frameworks | `express` |
151
+ | `fastify` | frameworks | `fastify` |
152
+ | `koa` | frameworks | `koa` |
153
+ | `hono` | frameworks | `hono` |
154
+ | `next` | frameworks | `nextjs` |
155
+ | `react` | frameworks | `react` |
156
+ | `vue` | frameworks | `vue` |
157
+ | `@prisma/client` | orm | `prisma` |
158
+ | `drizzle-orm` | orm | `drizzle` |
159
+ | `typeorm` | orm | `typeorm` |
160
+ | `mongoose` | orm | `mongoose` |
161
+ | `jsonwebtoken`, `jose` | auth | `jwt` |
162
+ | `passport` | auth | `passport` |
163
+
164
+ Write detected entries back into `settings.json` under `stack` (non-destructive — only updates `stack`, never touches `overrides`, `phaseLogging`, `autoDiscovery`, or `securityScan`).
165
+
166
+ If `autoDiscovery: false`, read `settings.json` as-is — no scan runs.
167
+
168
+ **Build `$MDD_STACK`** by merging `stack` + `overrides` into a flat deduplicated array of all values across all categories.
169
+
170
+ If the stack was newly detected or changed, report one line:
171
+ ```
172
+ 📋 Stack: typescript, node, express, prisma, jwt | phase logging: on | security scan: off
173
+ ```
174
+
175
+ **Phase logging gate:** All `mdd-log-phase.sh` calls throughout every mode file must be wrapped:
176
+ ```bash
177
+ [ "$MDD_PHASE_LOGGING" = "false" ] || bash ~/.claude/hooks/mdd-log-phase.sh ...
178
+ ```
179
+
101
180
  ## Step 0b — Detect Mode
102
181
 
103
182
  The user's full arguments are: **$ARGUMENTS**
@@ -136,10 +215,19 @@ Use whichever path contains `mdd-audit.md`. Store it as `$MDD_DIR` and use it fo
136
215
  - If arguments start with `bug` →
137
216
  **Read `$MDD_DIR/mdd-bug.md` then follow BUG MODE instructions.**
138
217
 
218
+ - If arguments start with `security-rules` →
219
+ **Read `$MDD_DIR/mdd-security-rules.md` then follow SECURITY RULES MODE instructions.**
220
+
139
221
  - If arguments are empty → ask the user what they want to do (build a feature, run an audit, check status, etc.)
140
222
 
141
223
  - Otherwise → **Read `$MDD_DIR/mdd-build.md` then follow BUILD MODE instructions.**
142
224
 
225
+ After mode is determined and before reading the mode file, log the invocation. `$ARGUMENTS` is the full argument string the user typed (e.g., `build user-auth`, `audit 03`, `status`):
226
+
227
+ ```bash
228
+ bash ~/.claude/hooks/mdd-log-phase.sh "mdd" "-" "invoked" "$ARGUMENTS"
229
+ ```
230
+
143
231
  ---
144
232
 
145
233
  ## Branch Guard (All Modes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thedecipherist/mdd",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "description": "MDD — Manual-Driven Development workflow for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {