@thedecipherist/mdd 1.7.2 → 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 +86 -0
- package/commands/mdd-audit.md +18 -0
- package/commands/mdd-build.md +16 -0
- package/commands/mdd-rules-express.md +23 -0
- package/commands/mdd-rules-jwt.md +23 -0
- package/commands/mdd-rules-prisma.md +23 -0
- package/commands/mdd-rules-typescript.md +30 -0
- package/commands/mdd-security-rules.md +303 -0
- package/commands/mdd.md +82 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -943,6 +943,7 @@ All MDD artifacts live in one place:
|
|
|
943
943
|
│ ├── agent-N-config.md
|
|
944
944
|
│ └── agent-N-notes.md
|
|
945
945
|
├── .startup.md # Auto-generated session context (read by Claude on start)
|
|
946
|
+
├── settings.json # Stack config — auto-populated by discovery, committed to git
|
|
946
947
|
└── connections.md # Pre-computed relationship map (path tree + Mermaid graph)
|
|
947
948
|
```
|
|
948
949
|
|
|
@@ -982,6 +983,91 @@ The auto-generated section above the `---` is rebuilt each time. The Notes secti
|
|
|
982
983
|
|
|
983
984
|
---
|
|
984
985
|
|
|
986
|
+
## Stack Settings
|
|
987
|
+
|
|
988
|
+
MDD creates `.mdd/settings.json` on first run. It controls which rule files load during audit and build phases, whether stack detection runs automatically, and a few other session behaviours.
|
|
989
|
+
|
|
990
|
+
```json
|
|
991
|
+
{
|
|
992
|
+
"autoDiscovery": true,
|
|
993
|
+
"stack": {
|
|
994
|
+
"language": ["typescript"],
|
|
995
|
+
"runtime": ["node"],
|
|
996
|
+
"frameworks": ["express"],
|
|
997
|
+
"orm": ["prisma"],
|
|
998
|
+
"auth": ["jwt"]
|
|
999
|
+
},
|
|
1000
|
+
"overrides": {},
|
|
1001
|
+
"phaseLogging": true,
|
|
1002
|
+
"securityScan": false
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
**`autoDiscovery: true` (default)** - MDD scans your manifest files once at session start and writes the detected stack into `settings.json`. It checks `package.json`, `go.mod`, `pyproject.toml`, and `composer.json`. The `stack` field is owned by discovery when this is on; `overrides` is always yours to edit.
|
|
1007
|
+
|
|
1008
|
+
**`autoDiscovery: false`** - MDD reads `settings.json` as-is. You control the stack manually. Useful when your dependencies don't reflect the full picture (custom implementations, monorepos, unusual setups).
|
|
1009
|
+
|
|
1010
|
+
**`overrides`** - Merged on top of `stack` at phase time. Add anything discovery misses - custom auth implementations, internal frameworks, or anything else that needs stack-specific rules.
|
|
1011
|
+
|
|
1012
|
+
**`phaseLogging: true` (default)** - Controls whether MDD writes phase timing data via `mdd-log-phase.sh`. Set to `false` to suppress all phase log output.
|
|
1013
|
+
|
|
1014
|
+
**`securityScan: false` (default)** - Enables the security rule generator. See the section below.
|
|
1015
|
+
|
|
1016
|
+
### Stack-Specific Rule Files
|
|
1017
|
+
|
|
1018
|
+
Once MDD knows your stack, it loads matching rule files at the start of each audit and build phase. These files live in `~/.claude/mdd/` alongside the other mode files and are installed with `mdd install`.
|
|
1019
|
+
|
|
1020
|
+
```
|
|
1021
|
+
mdd-rules-typescript.md # TypeScript-specific audit criteria and build checklists
|
|
1022
|
+
mdd-rules-express.md # Express error handling, middleware, route validation rules
|
|
1023
|
+
mdd-rules-jwt.md # JWT decode safety, expiry checks, secret validation
|
|
1024
|
+
mdd-rules-prisma.md # Prisma query safety, transaction patterns, migration checks
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
Rules are additive - they append criteria to the existing phase rather than replacing anything. If a rule file doesn't exist for a stack entry, MDD warns once and continues. A misconfigured or missing `settings.json` never halts a session.
|
|
1028
|
+
|
|
1029
|
+
### Security Rule Generator
|
|
1030
|
+
|
|
1031
|
+
When `securityScan: true`, MDD runs a vulnerability scan against your declared stack and generates new audit rules for any patterns not already covered.
|
|
1032
|
+
|
|
1033
|
+
```bash
|
|
1034
|
+
# Runs automatically during /mdd audit when securityScan: true
|
|
1035
|
+
# Or run directly:
|
|
1036
|
+
/mdd security-rules
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
**How it works:**
|
|
1040
|
+
|
|
1041
|
+
1. Scans your dependencies using free tools - `npm audit` for Node projects, `osv-scanner` for multi-language, Snyk CLI free tier if installed. No API keys required.
|
|
1042
|
+
2. Reads all existing `mdd-rules-{stack}.md` files and extracts the current rule set.
|
|
1043
|
+
3. For each vulnerability found: checks whether the attack vector is already covered by an existing rule.
|
|
1044
|
+
4. If a gap exists: generates a new rule targeting the vulnerability *class*, not just the specific CVE. A rule about "prototype pollution via unvalidated query parameters" will catch future variants, not just the package version that triggered it.
|
|
1045
|
+
5. Appends new rules to the relevant `mdd-rules-{stack}.md` file with the source CVE as a reference.
|
|
1046
|
+
|
|
1047
|
+
```
|
|
1048
|
+
Security rule generator - 2026-05-20
|
|
1049
|
+
|
|
1050
|
+
Sources: npm audit, osv-scanner
|
|
1051
|
+
Findings reviewed: 12
|
|
1052
|
+
Already covered by existing rules: 9
|
|
1053
|
+
Gaps found: 3
|
|
1054
|
+
|
|
1055
|
+
New rules generated:
|
|
1056
|
+
mdd-rules-express.md +2 rules
|
|
1057
|
+
- [P2] Prototype pollution via req.query or req.body merge — use structuredClone() or
|
|
1058
|
+
a safe merge utility. Reference: CVE-2024-29041
|
|
1059
|
+
- [P2] res.redirect() called with user-supplied path without allowlist validation.
|
|
1060
|
+
Reference: CVE-2024-43796
|
|
1061
|
+
|
|
1062
|
+
mdd-rules-jwt.md +1 rule
|
|
1063
|
+
- [P2] jwt.decode() used instead of jwt.verify() — decode skips signature check.
|
|
1064
|
+
Reference: CVE-2022-21449 (class)
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
The gap check uses semantic comparison, not string matching, so rules generated in a previous run are not duplicated even if they use different wording. Each generated rule includes the CVE ID so you can trace where it came from and review or remove it if needed.
|
|
1068
|
+
|
|
1069
|
+
---
|
|
1070
|
+
|
|
985
1071
|
## MDD Versioning
|
|
986
1072
|
|
|
987
1073
|
Every file created by MDD is stamped with `mdd_version: N` in its frontmatter. This tracks which version of the workflow created or last updated each doc.
|
package/commands/mdd-audit.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Triggered when arguments start with `audit`.
|
|
4
4
|
|
|
5
|
+
### Stack Rule Loading (runs before Phase A1)
|
|
6
|
+
|
|
7
|
+
After Step 0c in `mdd.md` sets `$MDD_STACK` and `$MDD_DIR`, load any matching stack rule files. This runs silently — no output unless a problem occurs.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
For each entry in $MDD_STACK:
|
|
11
|
+
If $MDD_DIR/mdd-rules-{entry}.md exists:
|
|
12
|
+
Read the file — append all rules found to the active audit criteria for this session
|
|
13
|
+
Else:
|
|
14
|
+
Emit one line: ⚠ No rule file for '{entry}' — skipping
|
|
15
|
+
Continue (never halt)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Stack rules are **additive only** — they extend the P1/P2/P3/P4 criteria lists used in Phase A3. They never replace or gate core audit behaviour.
|
|
19
|
+
|
|
20
|
+
If `$MDD_SECURITY_SCAN` is `true`, run the security rule generator before loading rules:
|
|
21
|
+
**Read `$MDD_DIR/mdd-security-rules.md` and follow its SECURITY RULES MODE instructions.** New rules it writes to `mdd-rules-{stack}.md` files will be picked up by the loading step above.
|
|
22
|
+
|
|
5
23
|
### Phase A1 — Scope
|
|
6
24
|
|
|
7
25
|
```bash
|
package/commands/mdd-build.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
## BUILD MODE — New Feature Development
|
|
2
2
|
|
|
3
|
+
### Stack Rule Loading (runs before Phase 0)
|
|
4
|
+
|
|
5
|
+
After Step 0c in `mdd.md` sets `$MDD_STACK` and `$MDD_DIR`, load any matching stack rule files. This runs silently — no output unless a problem occurs.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
For each entry in $MDD_STACK:
|
|
9
|
+
If $MDD_DIR/mdd-rules-{entry}.md exists:
|
|
10
|
+
Read the file — append all build checklist items to Phase 6 implementation steps
|
|
11
|
+
Append any additional audit criteria to Phase 7b verification checks
|
|
12
|
+
Else:
|
|
13
|
+
Emit one line: ⚠ No rule file for '{entry}' — skipping
|
|
14
|
+
Continue (never halt)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Stack rules are **additive only** — they extend Phase 6 checklists and Phase 7b verification. They never replace or gate core build behaviour.
|
|
18
|
+
|
|
3
19
|
### Phase 0 — Branch Safety Check
|
|
4
20
|
|
|
5
21
|
```bash
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## MDD Rules — Express
|
|
2
|
+
|
|
3
|
+
Rules loaded when `stack.frameworks` includes `express`. Applied additively to audit criteria and build checklists.
|
|
4
|
+
|
|
5
|
+
### Audit Criteria
|
|
6
|
+
|
|
7
|
+
#### P2 — Error Handler 5xx Leakage
|
|
8
|
+
- Express (or equivalent HTTP framework) error handler forwards raw `Error.message` to the client without differentiating expected errors (4xx) from unexpected errors (5xx). For responses with status >= 500, always return a generic message (`'Internal server error'`) — never the raw exception message. Exposing Prisma connection errors, stack traces, or internal paths to clients is a P2 finding.
|
|
9
|
+
|
|
10
|
+
#### P2 — Open Redirect
|
|
11
|
+
- `res.redirect()` called with a user-supplied path (from `req.query`, `req.body`, `req.params`) without allowlist validation. Any redirect target that an attacker can control is a P2 finding.
|
|
12
|
+
|
|
13
|
+
#### P2 — Prototype Pollution via Body/Query Merge
|
|
14
|
+
- `req.query` or `req.body` merged into a plain object using spread, `Object.assign`, or similar without sanitisation. Use `structuredClone()` or a safe merge utility. Reference: CVE-2024-29041 class.
|
|
15
|
+
|
|
16
|
+
#### P2 — Middleware Order
|
|
17
|
+
- Authentication or authorisation middleware registered after route handlers it is meant to protect — P2. Middleware order in Express is execution order; a route registered before `app.use(authMiddleware)` is unprotected.
|
|
18
|
+
|
|
19
|
+
### Build Checklist (Phase 6)
|
|
20
|
+
|
|
21
|
+
- **Error handler shape:** Error handler must differentiate 4xx vs 5xx. Never forward `err.message` for status >= 500.
|
|
22
|
+
- **Redirect validation:** Any `res.redirect()` that uses request-supplied data must validate against an explicit allowlist before redirecting.
|
|
23
|
+
- **Middleware order:** Register global middleware (auth, rate limiting, body parsing) before route handlers, not after.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## MDD Rules — JWT
|
|
2
|
+
|
|
3
|
+
Rules loaded when `stack.auth` includes `jwt`. Applied additively to audit criteria and build checklists.
|
|
4
|
+
|
|
5
|
+
### Audit Criteria
|
|
6
|
+
|
|
7
|
+
#### P2 — decode() Instead of verify()
|
|
8
|
+
- `jwt.decode()` used instead of `jwt.verify()` to process an incoming token — P2. `decode()` skips signature verification entirely. Any token, including a forged one, will appear valid. Always use `jwt.verify()` with the secret/public key.
|
|
9
|
+
|
|
10
|
+
#### P2 — Unsafe Type Assertion After verify()
|
|
11
|
+
- `jwt.verify()` result cast with a type assertion (`as JwtPayload`) without runtime narrowing on required fields — P2. `verify()` returns `string | JwtPayload`; casting directly sets fields to `undefined as string` when the payload shape doesn't match. Required fields (`sub`, `email`, `id`, etc.) must be checked with `typeof field === 'string'` before use.
|
|
12
|
+
|
|
13
|
+
#### P2 — Empty-String Secret Fallback
|
|
14
|
+
- JWT signing or verification called with an empty-string fallback for the secret (e.g. `process.env.JWT_SECRET || ''`) — P2. A server that signs tokens with `''` is equivalent to having no secret. Secret must be validated at startup.
|
|
15
|
+
|
|
16
|
+
#### P3 — Missing Token Expiry Check
|
|
17
|
+
- Token payload used without checking `exp` field when the library does not enforce it automatically — P3. Always pass `{ ignoreExpiration: false }` explicitly or verify the library's default behaviour.
|
|
18
|
+
|
|
19
|
+
### Build Checklist (Phase 6)
|
|
20
|
+
|
|
21
|
+
- **Always use verify(), never decode():** `jwt.decode()` is for inspecting token structure only — never for authentication.
|
|
22
|
+
- **Runtime narrowing after verify():** After `jwt.verify()`, check that all required payload fields exist and are the expected type before using them.
|
|
23
|
+
- **Secret at startup:** Add JWT secret(s) to `required_env` in the feature doc and add a startup validation block.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## MDD Rules — Prisma
|
|
2
|
+
|
|
3
|
+
Rules loaded when `stack.orm` includes `prisma`. Applied additively to audit criteria and build checklists.
|
|
4
|
+
|
|
5
|
+
### Audit Criteria
|
|
6
|
+
|
|
7
|
+
#### P2 — Raw Query Without Parameterisation
|
|
8
|
+
- `prisma.$queryRaw` or `prisma.$executeRaw` used with string interpolation (`` `SELECT ... WHERE id = ${userId}` ``) instead of tagged template literals or `Prisma.sql` — P2. Raw string interpolation bypasses Prisma's parameterisation and is vulnerable to SQL injection.
|
|
9
|
+
|
|
10
|
+
#### P2 — Missing Transaction for Multi-Step Writes
|
|
11
|
+
- Multiple `prisma.model.create/update/delete` calls in a single handler without wrapping in `prisma.$transaction()` — P2 when the operations must be atomic (e.g. deducting balance and creating a record).
|
|
12
|
+
|
|
13
|
+
#### P3 — PrismaClient Instantiated Per Request
|
|
14
|
+
- `new PrismaClient()` called inside a request handler or per-import in multiple files — P3. A single shared client instance should be created once (e.g. `src/lib/prisma.ts`) and imported everywhere. Multiple instances exhaust the connection pool.
|
|
15
|
+
|
|
16
|
+
#### P3 — Unhandled PrismaClientKnownRequestError
|
|
17
|
+
- Prisma operations in request handlers without catching `PrismaClientKnownRequestError` — P3. Uncaught Prisma errors surface as 500s with internal schema details in the default Express error handler.
|
|
18
|
+
|
|
19
|
+
### Build Checklist (Phase 6)
|
|
20
|
+
|
|
21
|
+
- **Single shared client:** Create `src/lib/prisma.ts` exporting one `PrismaClient` instance. Import it everywhere — never instantiate in handlers.
|
|
22
|
+
- **Transactions for atomic operations:** Any handler that writes to multiple tables must use `prisma.$transaction()`.
|
|
23
|
+
- **Catch Prisma errors:** Wrap Prisma calls in try/catch and handle `PrismaClientKnownRequestError` with appropriate HTTP responses.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## MDD Rules — TypeScript
|
|
2
|
+
|
|
3
|
+
Rules loaded when `stack.language` includes `typescript`. Applied additively to audit criteria and build checklists.
|
|
4
|
+
|
|
5
|
+
### Audit Criteria
|
|
6
|
+
|
|
7
|
+
#### P2 — Switch Exhaustiveness
|
|
8
|
+
- Switch statement on a string-union type or operation enum has no `default:` case — P2.
|
|
9
|
+
- Switch `default:` returns any value (`''`, `undefined`, a node, etc.) instead of throwing with `satisfies never` — P2. Correct pattern: `default: throw new Error(\`unhandled: \${x satisfies never}\`)`.
|
|
10
|
+
|
|
11
|
+
#### P2 — Type Assertion Safety
|
|
12
|
+
- Type assertion (`as T`) used on the result of an external decode operation (`jwt.verify`, `JSON.parse`, schema validation output) without runtime narrowing on required fields — P2. Required fields must be checked with `typeof field === 'string'` (or equivalent) before use.
|
|
13
|
+
|
|
14
|
+
#### P2 — Environment Startup Validation
|
|
15
|
+
- Required environment variable accessed with an empty-string or falsy fallback (e.g. `process.env.SECRET || ''`) instead of throwing at startup when absent — P2. A server that boots silently with a missing secret is a latent vulnerability.
|
|
16
|
+
|
|
17
|
+
#### P3 — Pattern Coverage in File
|
|
18
|
+
- When a finding of type X is found in a file, grep the entire file for all other instances of the same pattern before marking the finding complete. A single finding does not imply the rest of the file is clean — P3 if additional instances are present and unflagged.
|
|
19
|
+
|
|
20
|
+
### Build Checklist (Phase 6)
|
|
21
|
+
|
|
22
|
+
- **Env validation block:** If `required_env` is non-empty in the feature doc, add a startup validation block to the server entry point before any route registration:
|
|
23
|
+
```typescript
|
|
24
|
+
const REQUIRED_ENV = ['SECRET_KEY', 'DB_URL'];
|
|
25
|
+
for (const key of REQUIRED_ENV) {
|
|
26
|
+
if (!process.env[key]) throw new Error(`Missing required env var: ${key}`);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
- **Switch exhaustiveness:** Every switch on a string-union or enum type must have a `default: throw new Error(\`unhandled: \${x satisfies never}\`)` branch.
|
|
30
|
+
- **Shared utilities:** If `depends_on` is non-empty, scan the dependency's source files for shared infrastructure (error types, DB clients, utility functions). If the new feature would duplicate them, extract to a shared module before implementing.
|
|
@@ -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_DIR/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_DIR/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,6 +215,9 @@ 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.**
|