vanara 0.1.0 → 0.2.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.
Files changed (195) hide show
  1. package/README.md +14 -0
  2. package/bin/vanara.js +66 -2
  3. package/catalog/agents/api-designer/AGENT.md +140 -0
  4. package/catalog/agents/api-designer/examples/openapi-snippet.yaml +130 -0
  5. package/catalog/agents/api-designer/examples/review-notes.md +61 -0
  6. package/catalog/agents/api-designer/references/contract-and-openapi.md +82 -0
  7. package/catalog/agents/api-designer/references/design-checklist.md +68 -0
  8. package/catalog/agents/api-designer/references/versioning-and-evolution.md +63 -0
  9. package/catalog/agents/api-designer/scripts/lint-openapi.mjs +180 -0
  10. package/catalog/agents/code-reviewer/AGENT.md +168 -0
  11. package/catalog/agents/code-reviewer/examples/pr-comment-template.md +48 -0
  12. package/catalog/agents/code-reviewer/examples/sample-review-output.md +38 -0
  13. package/catalog/agents/code-reviewer/references/review-checklist.md +46 -0
  14. package/catalog/agents/code-reviewer/references/security-review.md +62 -0
  15. package/catalog/agents/code-reviewer/references/severity-rubric.md +63 -0
  16. package/catalog/agents/code-reviewer/scripts/review-guard.mjs +102 -0
  17. package/catalog/agents/debugger/AGENT.md +112 -0
  18. package/catalog/agents/debugger/examples/postmortem-template.md +59 -0
  19. package/catalog/agents/debugger/examples/repro-template.md +50 -0
  20. package/catalog/agents/debugger/references/bisection.md +63 -0
  21. package/catalog/agents/debugger/references/debugging-method.md +57 -0
  22. package/catalog/agents/debugger/references/observability.md +61 -0
  23. package/catalog/agents/debugger/scripts/parse-stacktrace.mjs +109 -0
  24. package/catalog/agents/pr-summarizer/AGENT.md +156 -0
  25. package/catalog/agents/pr-summarizer/examples/sample-pr-summary.md +46 -0
  26. package/catalog/agents/pr-summarizer/examples/weak-vs-strong-summary.md +68 -0
  27. package/catalog/agents/pr-summarizer/references/identifying-risk.md +68 -0
  28. package/catalog/agents/pr-summarizer/references/reading-the-diff.md +68 -0
  29. package/catalog/agents/pr-summarizer/references/test-plan-and-missing-tests.md +64 -0
  30. package/catalog/agents/pr-summarizer/references/writing-the-summary.md +63 -0
  31. package/catalog/agents/pr-summarizer/scripts/diff-risk.mjs +111 -0
  32. package/catalog/agents/refactoring-specialist/AGENT.md +185 -0
  33. package/catalog/agents/refactoring-specialist/examples/before-after.md +91 -0
  34. package/catalog/agents/refactoring-specialist/examples/refactor-plan.md +97 -0
  35. package/catalog/agents/refactoring-specialist/references/large-scale-refactor.md +50 -0
  36. package/catalog/agents/refactoring-specialist/references/safe-workflow.md +75 -0
  37. package/catalog/agents/refactoring-specialist/references/smell-catalog.md +76 -0
  38. package/catalog/agents/refactoring-specialist/scripts/detect-smells.mjs +150 -0
  39. package/catalog/agents/security-auditor/AGENT.md +168 -0
  40. package/catalog/agents/security-auditor/examples/audit-report.md +68 -0
  41. package/catalog/agents/security-auditor/examples/finding-template.md +31 -0
  42. package/catalog/agents/security-auditor/references/audit-workflow.md +116 -0
  43. package/catalog/agents/security-auditor/references/severity-and-reporting.md +56 -0
  44. package/catalog/agents/security-auditor/references/vuln-classes.md +129 -0
  45. package/catalog/agents/security-auditor/scripts/scan-secrets.mjs +86 -0
  46. package/catalog/agents/technical-writer/AGENT.md +163 -0
  47. package/catalog/agents/technical-writer/examples/how-to-example.md +76 -0
  48. package/catalog/agents/technical-writer/examples/reference-example.md +75 -0
  49. package/catalog/agents/technical-writer/references/clarity-and-style.md +52 -0
  50. package/catalog/agents/technical-writer/references/diataxis-and-doc-types.md +61 -0
  51. package/catalog/agents/technical-writer/references/structure-and-maintenance.md +59 -0
  52. package/catalog/agents/technical-writer/scripts/readability.mjs +103 -0
  53. package/catalog/agents/test-author/AGENT.md +177 -0
  54. package/catalog/agents/test-author/examples/test-plan.md +54 -0
  55. package/catalog/agents/test-author/examples/unit-test-example.ts +55 -0
  56. package/catalog/agents/test-author/references/aaa-and-naming.md +75 -0
  57. package/catalog/agents/test-author/references/mocking-and-fixtures.md +94 -0
  58. package/catalog/agents/test-author/references/test-types-and-pyramid.md +60 -0
  59. package/catalog/agents/test-author/scripts/check-coverage.mjs +145 -0
  60. package/catalog/agents/threat-modeler/AGENT.md +163 -0
  61. package/catalog/agents/threat-modeler/examples/dfd-example.md +55 -0
  62. package/catalog/agents/threat-modeler/examples/threat-model.md +81 -0
  63. package/catalog/agents/threat-modeler/references/dfd-and-trust-boundaries.md +76 -0
  64. package/catalog/agents/threat-modeler/references/mitigation-catalog.md +77 -0
  65. package/catalog/agents/threat-modeler/references/stride-method.md +68 -0
  66. package/catalog/agents/threat-modeler/scripts/stride-checklist.mjs +183 -0
  67. package/catalog/agents/vuln-scanner/AGENT.md +146 -0
  68. package/catalog/agents/vuln-scanner/examples/finding-template.md +57 -0
  69. package/catalog/agents/vuln-scanner/examples/scan-report.md +49 -0
  70. package/catalog/agents/vuln-scanner/references/remediation-and-severity.md +68 -0
  71. package/catalog/agents/vuln-scanner/references/scan-types-and-tools.md +83 -0
  72. package/catalog/agents/vuln-scanner/references/triage-and-false-positives.md +73 -0
  73. package/catalog/agents/vuln-scanner/scripts/parse-scan-results.mjs +133 -0
  74. package/catalog/packs/code-review-pack/PACK.md +100 -0
  75. package/catalog/packs/code-review-pack/examples/pr-flow-example.md +50 -0
  76. package/catalog/packs/code-review-pack/references/rollout-guide.md +40 -0
  77. package/catalog/packs/security-pack/PACK.md +128 -0
  78. package/catalog/packs/security-pack/examples/owasp-vuln-triage-and-fix.md +66 -0
  79. package/catalog/packs/security-pack/examples/threat-model-then-audit.md +77 -0
  80. package/catalog/packs/security-pack/references/choosing-the-right-tool.md +37 -0
  81. package/catalog/packs/security-pack/references/threat-model-to-remediation.md +69 -0
  82. package/catalog/packs/security-pack/references/workflow.md +56 -0
  83. package/catalog/skills/api-pagination/SKILL.md +83 -0
  84. package/catalog/skills/api-pagination/examples/keyset-queries.sql +38 -0
  85. package/catalog/skills/api-pagination/examples/response-shapes.md +51 -0
  86. package/catalog/skills/api-pagination/references/consuming-pages.md +39 -0
  87. package/catalog/skills/api-pagination/references/counting-and-totals.md +41 -0
  88. package/catalog/skills/api-pagination/references/cursor-encoding.md +34 -0
  89. package/catalog/skills/api-pagination/scripts/check-cursor.mjs +51 -0
  90. package/catalog/skills/caching-strategies/SKILL.md +180 -0
  91. package/catalog/skills/caching-strategies/examples/http-cache-headers.md +82 -0
  92. package/catalog/skills/caching-strategies/examples/redis-cache-aside.js +110 -0
  93. package/catalog/skills/caching-strategies/references/cache-patterns.md +93 -0
  94. package/catalog/skills/caching-strategies/references/eviction-and-ttl.md +67 -0
  95. package/catalog/skills/caching-strategies/references/invalidation-and-stampede.md +99 -0
  96. package/catalog/skills/caching-strategies/scripts/ttl-jitter.mjs +99 -0
  97. package/catalog/skills/conventional-commits/SKILL.md +135 -0
  98. package/catalog/skills/conventional-commits/examples/commit-examples.md +60 -0
  99. package/catalog/skills/conventional-commits/examples/commitlint.config.js +31 -0
  100. package/catalog/skills/conventional-commits/references/breaking-changes-semver.md +50 -0
  101. package/catalog/skills/conventional-commits/references/scoping-commits.md +42 -0
  102. package/catalog/skills/conventional-commits/references/spec.md +78 -0
  103. package/catalog/skills/conventional-commits/scripts/lint-commit.mjs +97 -0
  104. package/catalog/skills/database-migrations/SKILL.md +137 -0
  105. package/catalog/skills/database-migrations/examples/add-column-safe.sql +34 -0
  106. package/catalog/skills/database-migrations/examples/migration-config.example.json +27 -0
  107. package/catalog/skills/database-migrations/references/expand-contract.md +74 -0
  108. package/catalog/skills/database-migrations/references/rollback-and-safety.md +71 -0
  109. package/catalog/skills/database-migrations/references/zero-downtime-changes.md +84 -0
  110. package/catalog/skills/database-migrations/scripts/check-migration-reversible.mjs +139 -0
  111. package/catalog/skills/error-handling-patterns/SKILL.md +136 -0
  112. package/catalog/skills/error-handling-patterns/examples/express-error-middleware.js +93 -0
  113. package/catalog/skills/error-handling-patterns/examples/result-pattern.ts +81 -0
  114. package/catalog/skills/error-handling-patterns/references/error-taxonomy.md +91 -0
  115. package/catalog/skills/error-handling-patterns/references/language-patterns.md +102 -0
  116. package/catalog/skills/error-handling-patterns/references/retry-and-backoff.md +76 -0
  117. package/catalog/skills/error-handling-patterns/scripts/lint-empty-catch.mjs +113 -0
  118. package/catalog/skills/git-collaboration-workflows/SKILL.md +104 -0
  119. package/catalog/skills/git-collaboration-workflows/examples/branch-protection.md +48 -0
  120. package/catalog/skills/git-collaboration-workflows/examples/pr-review-playbook.md +48 -0
  121. package/catalog/skills/git-collaboration-workflows/references/branching-models.md +52 -0
  122. package/catalog/skills/git-collaboration-workflows/references/history-hygiene.md +44 -0
  123. package/catalog/skills/git-collaboration-workflows/scripts/check-branch-hygiene.mjs +62 -0
  124. package/catalog/skills/owasp-top10/SKILL.md +112 -0
  125. package/catalog/skills/owasp-top10/examples/sql-injection-fix.md +33 -0
  126. package/catalog/skills/owasp-top10/examples/xss-fix.md +27 -0
  127. package/catalog/skills/owasp-top10/references/access-control.md +36 -0
  128. package/catalog/skills/owasp-top10/references/crypto-failures.md +29 -0
  129. package/catalog/skills/owasp-top10/references/injection.md +39 -0
  130. package/catalog/skills/owasp-top10/references/ssrf-and-design.md +35 -0
  131. package/catalog/skills/owasp-top10/scripts/scan-injection.mjs +73 -0
  132. package/catalog/skills/prompt-engineering/SKILL.md +100 -0
  133. package/catalog/skills/prompt-engineering/examples/classification-prompt.md +29 -0
  134. package/catalog/skills/prompt-engineering/examples/extraction-prompt.md +25 -0
  135. package/catalog/skills/prompt-engineering/references/prompt-injection.md +34 -0
  136. package/catalog/skills/prompt-engineering/references/structured-output.md +34 -0
  137. package/catalog/skills/prompt-engineering/references/techniques.md +33 -0
  138. package/catalog/skills/prompt-engineering/scripts/validate-output.mjs +71 -0
  139. package/catalog/skills/readme-writing/SKILL.md +176 -0
  140. package/catalog/skills/readme-writing/examples/README.template.md +73 -0
  141. package/catalog/skills/readme-writing/examples/good-readme-example.md +95 -0
  142. package/catalog/skills/readme-writing/references/anatomy.md +107 -0
  143. package/catalog/skills/readme-writing/references/maintenance.md +68 -0
  144. package/catalog/skills/readme-writing/references/writing-style.md +79 -0
  145. package/catalog/skills/readme-writing/scripts/lint-readme.mjs +117 -0
  146. package/catalog/skills/refactoring-patterns/SKILL.md +178 -0
  147. package/catalog/skills/refactoring-patterns/examples/extract-function.before-after.js +57 -0
  148. package/catalog/skills/refactoring-patterns/examples/replace-conditional-with-polymorphism.before-after.ts +63 -0
  149. package/catalog/skills/refactoring-patterns/references/code-smells.md +92 -0
  150. package/catalog/skills/refactoring-patterns/references/refactoring-catalog.md +78 -0
  151. package/catalog/skills/refactoring-patterns/references/safe-workflow.md +74 -0
  152. package/catalog/skills/refactoring-patterns/scripts/detect-smells.mjs +137 -0
  153. package/catalog/skills/rest-api-design/SKILL.md +147 -0
  154. package/catalog/skills/rest-api-design/examples/error-envelope.json +26 -0
  155. package/catalog/skills/rest-api-design/examples/orders-api.openapi.yaml +106 -0
  156. package/catalog/skills/rest-api-design/references/error-handling.md +43 -0
  157. package/catalog/skills/rest-api-design/references/pagination.md +41 -0
  158. package/catalog/skills/rest-api-design/references/status-codes.md +32 -0
  159. package/catalog/skills/rest-api-design/references/versioning.md +31 -0
  160. package/catalog/skills/rest-api-design/scripts/check-envelope.mjs +62 -0
  161. package/catalog/skills/secrets-management/SKILL.md +134 -0
  162. package/catalog/skills/secrets-management/examples/env-example.md +41 -0
  163. package/catalog/skills/secrets-management/examples/rotation-runbook.md +50 -0
  164. package/catalog/skills/secrets-management/references/leak-response.md +47 -0
  165. package/catalog/skills/secrets-management/references/rotation.md +46 -0
  166. package/catalog/skills/secrets-management/references/secret-managers.md +52 -0
  167. package/catalog/skills/secrets-management/scripts/detect-hardcoded.mjs +96 -0
  168. package/catalog/skills/secure-auth/SKILL.md +135 -0
  169. package/catalog/skills/secure-auth/examples/auth-flow.md +60 -0
  170. package/catalog/skills/secure-auth/examples/jwt-verify.ts +89 -0
  171. package/catalog/skills/secure-auth/references/auth-attacks.md +76 -0
  172. package/catalog/skills/secure-auth/references/oauth2-oidc.md +65 -0
  173. package/catalog/skills/secure-auth/references/password-hashing.md +70 -0
  174. package/catalog/skills/secure-auth/references/sessions-vs-jwt.md +69 -0
  175. package/catalog/skills/secure-auth/scripts/check-password-policy.mjs +114 -0
  176. package/catalog/skills/sql-index-tuning/SKILL.md +171 -0
  177. package/catalog/skills/sql-index-tuning/examples/explain-walkthrough.sql +91 -0
  178. package/catalog/skills/sql-index-tuning/examples/index-ddl.sql +67 -0
  179. package/catalog/skills/sql-index-tuning/references/btree-internals.md +58 -0
  180. package/catalog/skills/sql-index-tuning/references/composite-and-covering.md +60 -0
  181. package/catalog/skills/sql-index-tuning/references/reading-explain.md +54 -0
  182. package/catalog/skills/sql-index-tuning/references/selectivity-and-cardinality.md +62 -0
  183. package/catalog/skills/sql-index-tuning/scripts/suggest-index.mjs +213 -0
  184. package/catalog/skills/test-plan-design/SKILL.md +154 -0
  185. package/catalog/skills/test-plan-design/examples/test-case-template.md +45 -0
  186. package/catalog/skills/test-plan-design/examples/test-plan.md +57 -0
  187. package/catalog/skills/test-plan-design/references/case-design.md +79 -0
  188. package/catalog/skills/test-plan-design/references/risk-prioritization.md +58 -0
  189. package/catalog/skills/test-plan-design/references/test-pyramid.md +58 -0
  190. package/catalog/skills/test-plan-design/scripts/coverage-gaps.mjs +109 -0
  191. package/free-tier.json +15 -0
  192. package/package.json +15 -4
  193. package/premium-index.json +1 -0
  194. package/src/config.js +31 -1
  195. package/src/license.js +143 -0
@@ -0,0 +1,73 @@
1
+ # Triage and False Positives
2
+
3
+ Triage is the difference between a security agent and a `grep` wrapper. Raw scanner output is a starting
4
+ point with a high noise floor; this reference is how you turn it into signal.
5
+
6
+ ## The triage pipeline
7
+
8
+ ```text
9
+ raw findings
10
+ -> normalize (one schema: id, package, severity, location, source-tool)
11
+ -> dedupe (same CVE from SCA + container scan = one finding)
12
+ -> false-positive filter (suppress with a recorded reason)
13
+ -> reachability assessment (is the vulnerable path used/exposed?)
14
+ -> final severity (CVSS adjusted by reachability + impact)
15
+ -> rank + bucket (fix now / plan / accept-monitor)
16
+ ```
17
+
18
+ The bundled `scripts/parse-scan-results.mjs` automates normalize + dedupe + severity sort. Human
19
+ judgment owns the false-positive and reachability steps.
20
+
21
+ ## Deduplication
22
+
23
+ The same underlying CVE often appears from multiple tools and multiple dependency paths. Collapse them:
24
+
25
+ - Key on the advisory ID (CVE / GHSA) **plus** the affected package+version.
26
+ - Keep the highest-confidence source and union the locations.
27
+ - Count once in the summary. Reporting one CVE five times manufactures fake urgency and is a form of
28
+ alert fatigue.
29
+
30
+ ## Identifying false positives
31
+
32
+ Common false positives and how to confirm them:
33
+
34
+ | Pattern | Why it's flagged | How to confirm it's benign |
35
+ |---|---|---|
36
+ | Example/placeholder secret | Matches a key regex | Value is `AKIAEXAMPLE…`, in `*.example`, or in test fixtures |
37
+ | Vendored test data | CVE in a bundled sample | Path is under `test/`, `fixtures/`, `__mocks__/` and not shipped |
38
+ | Unreachable CVE | Vulnerable function never called | `grep` for the import/call; if absent, code path is dead |
39
+ | Dev-only dependency | CVE in a build/test tool | Not in production bundle; lower severity, not zero |
40
+ | Feature-gated advisory | Applies only with a flag on | Confirm the flag/default; note the qualifier |
41
+
42
+ **Always record the suppression reason.** A silently dropped finding is indistinguishable from a missed
43
+ one. Put suppressed items in an "Accepted / suppressed" section with their justification.
44
+
45
+ ## Reachability — the multiplier
46
+
47
+ Reachability is what separates theoretical from exploitable risk:
48
+
49
+ - **Exposed + reachable** (e.g. a deserialization CVE on a public POST handler): keep or raise severity.
50
+ - **Present but unreachable** (vulnerable parser never invoked): downgrade, schedule, don't block.
51
+ - **Dev/build-time only:** real but lower; an attacker needs supply-chain or CI access to exploit.
52
+
53
+ State the reasoning explicitly so a reviewer can overrule:
54
+
55
+ > CVE-2025-XXXX (CVSS 9.8) in `fast-xml@3.1.0`. The vulnerable `parseAttrs` path is only hit when
56
+ > `allowAttributes:true`; this app calls the parser with defaults. **Downgraded critical -> medium**,
57
+ > scheduled, not release-blocking.
58
+
59
+ ## Avoiding alert fatigue
60
+
61
+ - Hard-rank. The top finding must be the genuinely most urgent, not the highest raw CVSS.
62
+ - Bucket aggressively: *fix now* should be short (single digits ideally). Everything else goes to
63
+ *plan* or *accept/monitor*.
64
+ - Push low/info findings into an appendix. Do not make a reader scroll past 200 lows to find the one
65
+ critical — that is how real issues get missed.
66
+ - Be consistent run-to-run so developers can diff reports and see what's new.
67
+
68
+ ## When triage is genuinely uncertain
69
+
70
+ If you cannot determine reachability from static analysis alone, say so and assign provisional severity
71
+ on the conservative side, with a note on what would resolve the uncertainty (e.g. "needs DAST against
72
+ staging" or "confirm whether `module X` is in the production bundle"). Honest uncertainty beats false
73
+ confidence in either direction.
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ // Normalizes a list of raw vulnerability findings: dedupes and sorts by
3
+ // triaged severity. Demonstrates the triage step (raw output -> ranked list)
4
+ // from this agent package. Zero dependencies; Node built-ins only.
5
+ //
6
+ // Usage:
7
+ // node parse-scan-results.mjs findings.json # read a JSON array from a file
8
+ // cat findings.json | node parse-scan-results.mjs # read from stdin
9
+ // node parse-scan-results.mjs --selftest # run built-in tests (exit 0/1)
10
+ //
11
+ // Input: JSON array of findings, each with at least { id, severity }.
12
+ // Optional fields: package, location, cvss, reachability, fix, bucket.
13
+ // Output: { total, deduped, bySeverity, findings } sorted critical -> info.
14
+
15
+ const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low', 'info'];
16
+ const SEVERITY_RANK = Object.fromEntries(SEVERITY_ORDER.map((s, i) => [s, i]));
17
+
18
+ function normalizeSeverity(sev) {
19
+ const s = String(sev ?? '').toLowerCase().trim();
20
+ return SEVERITY_RANK[s] !== undefined ? s : 'info';
21
+ }
22
+
23
+ // Dedup key: advisory id + affected package/location. The same CVE reported by
24
+ // multiple tools or via multiple paths collapses to one finding.
25
+ function dedupKey(f) {
26
+ const id = String(f.id ?? 'unknown').toLowerCase();
27
+ const where = String(f.package ?? f.location ?? '').toLowerCase();
28
+ return `${id}::${where}`;
29
+ }
30
+
31
+ export function parseFindings(rawList) {
32
+ if (!Array.isArray(rawList)) {
33
+ throw new Error('input must be a JSON array of findings');
34
+ }
35
+
36
+ const seen = new Map();
37
+ for (const raw of rawList) {
38
+ const finding = { ...raw, severity: normalizeSeverity(raw.severity) };
39
+ const key = dedupKey(finding);
40
+ if (!seen.has(key)) {
41
+ seen.set(key, finding);
42
+ } else {
43
+ // Keep the higher severity when duplicates disagree.
44
+ const existing = seen.get(key);
45
+ if (SEVERITY_RANK[finding.severity] < SEVERITY_RANK[existing.severity]) {
46
+ seen.set(key, finding);
47
+ }
48
+ }
49
+ }
50
+
51
+ const findings = [...seen.values()].sort((a, b) => {
52
+ const d = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
53
+ if (d !== 0) return d;
54
+ return String(a.id ?? '').localeCompare(String(b.id ?? ''));
55
+ });
56
+
57
+ const bySeverity = Object.fromEntries(SEVERITY_ORDER.map((s) => [s, 0]));
58
+ for (const f of findings) bySeverity[f.severity] += 1;
59
+
60
+ return { total: rawList.length, deduped: findings.length, bySeverity, findings };
61
+ }
62
+
63
+ function run(json) {
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(json);
67
+ } catch {
68
+ console.error('✗ invalid JSON input');
69
+ process.exit(1);
70
+ }
71
+ try {
72
+ const result = parseFindings(parsed);
73
+ console.log(JSON.stringify(result, null, 2));
74
+ process.exit(0);
75
+ } catch (err) {
76
+ console.error(`✗ ${err.message}`);
77
+ process.exit(1);
78
+ }
79
+ }
80
+
81
+ function selftest() {
82
+ const raw = [
83
+ { id: 'CVE-1', package: 'lodash@4.17.15', severity: 'high' },
84
+ { id: 'CVE-1', package: 'lodash@4.17.15', severity: 'medium' }, // dup, lower sev
85
+ { id: 'CVE-2', package: 'axios@0.21.0', severity: 'CRITICAL' }, // case-insensitive
86
+ { id: 'CVE-3', package: 'jest@29', severity: 'low' },
87
+ { id: 'SECRET-1', location: 'config/x.env', severity: 'critical' },
88
+ { id: 'CVE-4', package: 'debug@4', severity: 'weird' }, // -> info
89
+ ];
90
+
91
+ const result = parseFindings(raw);
92
+ const checks = [];
93
+
94
+ // Dedup: 6 raw -> 5 unique (the two CVE-1 collapse).
95
+ checks.push(['dedupes duplicates', result.total === 6 && result.deduped === 5]);
96
+
97
+ // Duplicate keeps the higher severity (high, not medium).
98
+ const cve1 = result.findings.find((f) => f.id === 'CVE-1');
99
+ checks.push(['dup keeps higher severity', cve1 && cve1.severity === 'high']);
100
+
101
+ // Severity counts after normalization.
102
+ checks.push(['critical count is 2', result.bySeverity.critical === 2]);
103
+ checks.push(['high count is 1', result.bySeverity.high === 1]);
104
+ checks.push(['unknown severity -> info', result.bySeverity.info === 1]);
105
+
106
+ // Sort order: criticals first, info last.
107
+ const order = result.findings.map((f) => f.severity);
108
+ const sorted = [...order].sort(
109
+ (a, b) => SEVERITY_RANK[a] - SEVERITY_RANK[b]
110
+ );
111
+ checks.push(['sorted by severity', JSON.stringify(order) === JSON.stringify(sorted)]);
112
+ checks.push(['first finding is critical', order[0] === 'critical']);
113
+ checks.push(['last finding is info', order[order.length - 1] === 'info']);
114
+
115
+ let ok = true;
116
+ for (const [label, passed] of checks) {
117
+ console.log(`${passed ? '✓' : '✗'} ${label}`);
118
+ ok &&= passed;
119
+ }
120
+ process.exit(ok ? 0 : 1);
121
+ }
122
+
123
+ const arg = process.argv[2];
124
+ if (arg === '--selftest') {
125
+ selftest();
126
+ } else if (arg) {
127
+ const { readFileSync } = await import('node:fs');
128
+ run(readFileSync(arg, 'utf8'));
129
+ } else {
130
+ let buf = '';
131
+ process.stdin.on('data', (c) => (buf += c));
132
+ process.stdin.on('end', () => run(buf));
133
+ }
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: code-review-pack
3
+ description: Review pull requests faster and better — automated first-pass review, PR summaries reviewers can trust, healthy git workflow settings, conventional history, and query-performance checks, sequenced into one review pipeline.
4
+ type: pack
5
+ version: 1.0.0
6
+ updated: 2026-07-10
7
+ agents: [code-reviewer, pr-summarizer]
8
+ skills: [git-collaboration-workflows, conventional-commits, sql-index-tuning]
9
+ ---
10
+ # Code Review Pack
11
+
12
+ The review toolkit for teams whose bottleneck is the pull-request queue: get every PR a
13
+ fast, rigorous first pass, give human reviewers a summary they can trust, and keep the
14
+ workflow settings and history hygiene that make review scale. The value of the pack is the
15
+ **sequence** — which item runs at which moment of a PR's life — not the item list.
16
+
17
+ ## Who this is for
18
+
19
+ Teams merging more PRs than their senior reviewers can deeply read; leads who want review
20
+ latency down without review quality collapsing; anyone inheriting a repo where "review"
21
+ means LGTM-stamping.
22
+
23
+ ## What's included
24
+
25
+ - **Agents:** `code-reviewer` (rigorous first-pass review: correctness, security, tests,
26
+ maintainability), `pr-summarizer` (what changed, why, risk areas — the reviewer's map).
27
+ - **Skills:** `git-collaboration-workflows` (branch protection, PR sizing, merge strategy,
28
+ monorepo questions), `conventional-commits` (machine-readable history that powers
29
+ changelogs and bisect), `sql-index-tuning` (the query-performance review lens most teams
30
+ lack — N+1s and missing indexes are the top silent regressions PRs ship).
31
+
32
+ ## The review pipeline (how the pieces sequence)
33
+
34
+ ```text
35
+ PR opened
36
+ 1. pr-summarizer → posts the map: what changed, why, blast radius, files to read first
37
+ 2. code-reviewer → first-pass findings: blocking / question / nit, with file:line
38
+ └─ SQL touched? → sql-index-tuning lens: EXPLAIN the new queries, check indexes
39
+ 3. human reviewer → reads the summary + findings, spends attention ONLY on judgment
40
+ calls the machine flagged and the design questions it can't make
41
+ merge
42
+ 4. conventional-commits → squash message feeds changelog + bisect-friendly history
43
+ weekly
44
+ 5. git-collaboration-workflows → hygiene audit: branch age, PR size trend, protection drift
45
+ ```
46
+
47
+ The division of labor is the point: agents do coverage (every line, every time), humans do
48
+ judgment (architecture, product fit, taste). Teams that flip this — humans doing coverage,
49
+ no machine pass — get slow reviews AND missed bugs.
50
+
51
+ ## Setup
52
+
53
+ 1. Install the pack; wire `pr-summarizer` + `code-reviewer` to run on PR-open (CI job or
54
+ scheduled agent).
55
+ 2. Apply the branch-protection settings from `git-collaboration-workflows`
56
+ (`examples/branch-protection.md`) — required checks, stale-review dismissal, squash-only.
57
+ 3. Adopt the `blocking:/q:/nit:` comment convention from the PR review playbook; announce
58
+ the review SLO (first response < 4h).
59
+ 4. Enable commit-lint per `conventional-commits` so the history contract is enforced, not
60
+ hoped for.
61
+
62
+ ## Comment convention (adopt verbatim)
63
+
64
+ ```text
65
+ blocking: must be fixed before merge — correctness, security, data loss
66
+ q: genuine question; answer may resolve it
67
+ nit: author's call; NEVER blocks
68
+ (unprefixed comments read as blocking — prefix everything)
69
+ ```
70
+
71
+ ## Pitfalls
72
+
73
+ - **LGTM-stamping past machine findings** — the pipeline's failure mode; blocking findings
74
+ must actually block, or reverts won't move.
75
+ - **Bot-nit fatigue** — untuned severity turns the first-pass reviewer into noise; demote
76
+ misfiring rules weekly during rollout (see `references/rollout-guide.md`).
77
+ - **Summaries as gospel on giant PRs** — a wrong map on a 2,000-line PR is an argument for
78
+ the size norms, not against the mapper.
79
+ - **Skipping the SQL lens** — missing-index regressions pass every test and fail at the
80
+ 10× tenant; the `EXPLAIN` step is not optional when queries change.
81
+ - **Anti-pattern: humans doing coverage** — re-reading every line the machine read wastes
82
+ the scarce resource (judgment) on the abundant one (attention).
83
+
84
+ ## When NOT to use this shape
85
+
86
+ Solo projects (review theater), and rubber-stamp compliance environments where findings
87
+ can't block merges anyway — fix the process authority first, then install tooling.
88
+ Edge case worth naming: generated code and vendored files should be excluded from both
89
+ summary and review scope up front, or they drown every real finding.
90
+
91
+ ## Verification
92
+
93
+ After two weeks, three numbers should move: median time-to-first-review (down), PR size p50
94
+ (down — the summary+size norms push authors to split), and post-merge defect
95
+ reverts (down). If reverts didn't move, check whether `code-reviewer` findings are being
96
+ LGTM'd past — the pipeline only works when blocking findings block. A full worked PR
97
+ walkthrough lives in [examples/pr-flow-example.md](examples/pr-flow-example.md).
98
+
99
+ Pairs with the `secure-delivery-pack` (security-focused review lenses) and
100
+ `craftsmanship-pack` (refactoring and test depth beyond the review gate).
@@ -0,0 +1,50 @@
1
+ # Worked Example — one PR through the pipeline
2
+
3
+ PR #512: "Add invoice CSV export" — 380 lines, touches API + a new query + one UI table.
4
+
5
+ ## 1. pr-summarizer posts (2 min after open)
6
+
7
+ > **What**: New `GET /invoices/export.csv` endpoint (streaming), export button on the
8
+ > invoices table, `invoices_export` permission.
9
+ > **Why**: FEAT-201 — finance team needs monthly exports.
10
+ > **Read first**: `export-controller.ts` (new query!), `permissions.ts` (auth change).
11
+ > **Blast radius**: read-only endpoint; new DB query on `invoices` (potentially large);
12
+ > no schema change. **Risk**: memory on large exports, query performance.
13
+
14
+ ## 2. code-reviewer posts (5 min after open)
15
+
16
+ > `blocking:` export-controller.ts:41 — query selects `*` and loads all rows into memory
17
+ > before streaming; a 500k-invoice tenant OOMs the pod. Stream with a cursor
18
+ > (keyset pagination) or use the DB driver's row stream.
19
+ >
20
+ > `blocking:` export-query.sql — `WHERE tenant_id = ? AND created_at > ?` has no
21
+ > supporting composite index (per sql-index-tuning: needs `(tenant_id, created_at)`;
22
+ > current plan is a seq scan at 2M rows — EXPLAIN attached).
23
+ >
24
+ > `q:` permissions.ts:88 — `invoices_export` granted to `member` by default; FEAT-201
25
+ > says finance-only. Intended?
26
+ >
27
+ > `nit:` csv column headers hardcoded English — project is i18n'd (`internationalization`).
28
+
29
+ ## 3. Human reviewer (18 min later, reads summary + findings first)
30
+
31
+ Spends attention on the two judgment calls: confirms permission should be finance-only
32
+ (product check), and decides streaming approach (driver row-stream over keyset — simpler
33
+ here). Doesn't re-read the 300 lines the machine already covered line-by-line; spot-checks
34
+ the test file.
35
+
36
+ ## 4. Author revises → checks green → squash-merge
37
+
38
+ ```text
39
+ feat(invoices): add streaming CSV export (FEAT-201)
40
+
41
+ Finance-only permission; keyset-cursor streaming; composite index
42
+ (tenant_id, created_at) added expand-only.
43
+ ```
44
+
45
+ ## Outcome ledger
46
+
47
+ - OOM prevented pre-merge (would have been a Sev-2 at month-end).
48
+ - Missing index caught before it met the 2M-row tenant.
49
+ - Human review time: ~20 min for a 380-line PR, spent entirely on judgment.
50
+ - History entry supports changelog + bisect.
@@ -0,0 +1,40 @@
1
+ # Rollout Guide
2
+
3
+ Review culture changes fail when imposed all at once. The two-week sequence that lands:
4
+
5
+ ## Week 1 — observation mode
6
+
7
+ - Wire `pr-summarizer` only. Zero friction, immediate value: every PR gets a map. Nobody
8
+ argues with a summary.
9
+ - Run `code-reviewer` in **comment-only** mode (findings posted, nothing blocks). The team
10
+ calibrates trust: are the findings real? (Tune severity thresholds now, while stakes are
11
+ low.)
12
+ - Baseline the three metrics: time-to-first-review, PR size p50, revert rate. You need the
13
+ "before" or the "after" is a vibe.
14
+
15
+ ## Week 2 — the contract
16
+
17
+ - Turn on branch protection per the skill's settings (checks required, stale-dismissal,
18
+ squash-only). Announce it as config, with the escape hatch documented (hotfix label path).
19
+ - Adopt comment prefixes (`blocking:/q:/nit:`) and the 4-hour first-response SLO — put the
20
+ SLO on a dashboard where standup sees it.
21
+ - commit-lint on (warning for 3 days, then required).
22
+
23
+ ## Weeks 3+ — enforcement earned
24
+
25
+ - `code-reviewer` blocking findings now actually block (the team has seen two weeks of its
26
+ judgment; false-positive rate is known and tuned).
27
+ - Weekly hygiene audit (`check-branch-hygiene.mjs`) posted to the team channel — trends,
28
+ not blame.
29
+
30
+ ## Objection handling (you will hear these)
31
+
32
+ - **"The bot nitpicks."** Nits never block — that's what the prefix system encodes. Tune or
33
+ demote rules that misfire; the config is code-reviewable.
34
+ - **"Summaries are wrong sometimes."** They're maps, not territory — a wrong summary on a
35
+ 2,000-line PR is an argument for smaller PRs, which the size norms fix.
36
+ - **"This slows us down."** The SLO + protection settings measurably speed up the queue;
37
+ show the week-1 baseline vs week-4. What slows teams is review latency and re-review
38
+ churn, both of which drop.
39
+ - **"Admins need bypass."** The bypass IS the vulnerability; the hotfix path exists and is
40
+ faster than arguing at 2 a.m.
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: security-pack
3
+ description: Build and ship secure software — threat modeling at design time, OWASP code audits, dependency/secret scanning, and secure auth and secrets handling, sequenced into one shift-left workflow.
4
+ type: pack
5
+ version: 2.0.0
6
+ updated: 2026-07-06
7
+ agents: [threat-modeler, security-auditor, vuln-scanner]
8
+ skills: [owasp-top10, secure-auth, secrets-management]
9
+ ---
10
+ # Security Pack
11
+
12
+ Security is cheapest when it moves **left** — designed in at the whiteboard, enforced while the code
13
+ is written, verified before merge, and monitored in the supply chain. This pack bundles three agents
14
+ and three skills that cover that whole arc and, more importantly, teaches **which one to reach for at
15
+ each stage of a feature's life**.
16
+
17
+ The value of a pack is not the list of items — it is the sequencing. A threat model with no audit is
18
+ a document nobody enforces; an audit with no threat model is a checklist with no context. This
19
+ document ties them into one repeatable loop: **model → build → audit → scan → ship**.
20
+
21
+ ## Who this is for
22
+
23
+ - Security engineers who own the security posture of a product or service.
24
+ - Developers who own the security of the features they build — the ones expected to threat-model,
25
+ self-audit, and keep their dependencies clean before asking for review.
26
+ - Tech leads introducing a shift-left practice to a team that currently bolts security on at the end.
27
+
28
+ If you only need a one-off review of an existing diff, the `security-auditor` agent alone is enough.
29
+ Reach for the full pack when you are carrying a feature **from design through to a clean merge** and
30
+ want security enforced at every handoff instead of a single gate at the finish line.
31
+
32
+ ## What's included
33
+
34
+ | Item | Kind | Job in the workflow |
35
+ |---|---|---|
36
+ | `threat-modeler` | agent | STRIDE-style threat modeling at design time — enumerate attack surface, trust boundaries, and abuse cases before code exists |
37
+ | `security-auditor` | agent | OWASP-focused code review of the diff — injection, auth flaws, broken access control, unsafe crypto |
38
+ | `vuln-scanner` | agent | Dependency CVE scanning and secret detection across the tree and git history |
39
+ | `owasp-top10` | skill | Per-risk defenses for the OWASP Top 10 — the reference the auditor and you apply |
40
+ | `secure-auth` | skill | Correct password hashing, session/token handling, and MFA construction |
41
+ | `secrets-management` | skill | Keep secrets out of source, load them safely, and rotate without downtime |
42
+
43
+ Agents *do* the work; skills are the *reference* they (and you) apply while doing it. Pair an agent
44
+ with its matching skill — `security-auditor` + `owasp-top10`, or building auth with `secure-auth` —
45
+ so the output is both produced and grounded in a standard.
46
+
47
+ ## The end-to-end workflow
48
+
49
+ A secure feature moves through five stages. The pack maps one primary item to each:
50
+
51
+ ```text
52
+ 1. Model → threat-modeler (+ owasp-top10) enumerate threats before code
53
+ 2. Build → secure-auth / secrets-management implement the risky parts correctly
54
+ 3. Audit → security-auditor (+ owasp-top10) review the diff, security-first
55
+ 4. Scan → vuln-scanner CVEs in deps + secrets in the tree
56
+ 5. Ship → gate on: no open CRITICAL/HIGH, clean scan
57
+ ```
58
+
59
+ Model **first**, before any code. A threat model produced after the feature is built is a
60
+ rationalization, not a design tool — its whole purpose is to change what you build. The threats it
61
+ surfaces become the audit checklist in stage 3 and the abuse-case tests you write while building.
62
+
63
+ ### Worked example: adding "password reset via email link"
64
+
65
+ ```text
66
+ threat-modeler → surfaces: token guessability, token reuse/replay, host-header poisoning in the
67
+ reset link, user enumeration on the "email sent" response, rate-limit bypass
68
+ owasp-top10 → maps those to A01 (access control), A07 (auth failures), A04 (insecure design)
69
+ secure-auth → reset token = 256-bit random, single-use, 15-min TTL, hashed at rest; response is
70
+ identical whether or not the email exists (no enumeration)
71
+ secrets-management → the email-provider API key is loaded from the secret store, never in the repo
72
+ security-auditor → confirms token is compared in constant time, is invalidated on use, and the
73
+ "sent" response leaks nothing; flags the missing rate limit as HIGH
74
+ vuln-scanner → the email SDK pulls a transitive dep with a known SSRF CVE — flagged, bump required
75
+ ```
76
+
77
+ Every item earned its place: the threat model set the agenda, the skills shaped the implementation,
78
+ the auditor verified it, and the scanner caught a supply-chain issue no human review would have.
79
+
80
+ ## How to choose the right item
81
+
82
+ - **Designing a feature that touches auth, money, or user data?** `threat-modeler` + `owasp-top10`
83
+ first — enumerate abuse cases before a line is written.
84
+ - **Implementing login, sessions, or MFA?** `secure-auth` — do not hand-roll password hashing or
85
+ token generation.
86
+ - **About to reference a credential, key, or token?** `secrets-management` — it never belongs in
87
+ source, config committed to git, or a client bundle.
88
+ - **Diff ready for review?** `security-auditor` — security pass before correctness, every time.
89
+ - **Before merge, always?** `vuln-scanner` — CVEs and leaked secrets are invisible to code review.
90
+
91
+ See `references/choosing-the-right-tool.md` for the full decision table, `references/workflow.md` for
92
+ the stage-by-stage playbook, and `references/threat-model-to-remediation.md` for the discipline
93
+ playbook that connects a threat model to concrete fixes. Two complete runs are in `examples/`.
94
+
95
+ ## Common pitfalls (anti-patterns this pack prevents)
96
+
97
+ - **Threat-modeling after the build.** A model written to justify existing code enumerates the
98
+ threats you already handled and misses the ones you didn't. The workflow puts it at stage 1 so it
99
+ can actually change the design.
100
+ - **Auditing without a threat model.** A generic OWASP checklist misses domain abuse cases (e.g. a
101
+ refund flow that can go negative). The threat model *is* the audit's context.
102
+ - **Treating secret scanning as one-time.** A secret committed once lives in git history forever even
103
+ after it's deleted from HEAD. `vuln-scanner` checks history, and the fix is rotation, not just
104
+ removal.
105
+ - **Rolling your own crypto/auth.** Hand-rolled password hashing, JWT verification, or token
106
+ comparison is a reliable source of criticals. `secure-auth` points to vetted constructions.
107
+ - **Fixing the secret, forgetting to rotate.** Deleting a leaked key from source does not un-leak it.
108
+ Any exposed secret must be rotated, not just removed.
109
+
110
+ ## When NOT to use this pack
111
+
112
+ - **Compliance/audit-trail work** (GDPR, SOC 2 evidence) — that's `compliance-auditor` and the
113
+ privacy/audit-logging skills, not this pack's application-security focus.
114
+ - **Infrastructure and network hardening** — firewall rules, TLS termination, and cloud IAM are
115
+ DevOps/network territory, not application-code security.
116
+ - **A one-line, no-risk change** — a copy tweak or a CSS fix does not need a threat model; a single
117
+ `security-auditor` pass (or nothing) is proportionate.
118
+ - **Smart-contract security** — use `smart-contract-auditor` and `smart-contract-security`; the
119
+ threat surface there is different.
120
+
121
+ ## Files in this package
122
+
123
+ - `references/workflow.md` — the five-stage security workflow in detail, with entry/exit criteria.
124
+ - `references/choosing-the-right-tool.md` — full decision table: symptom → item.
125
+ - `references/threat-model-to-remediation.md` — the discipline playbook connecting STRIDE threats to
126
+ audited, tested remediations.
127
+ - `examples/threat-model-then-audit.md` — a feature threat-modeled at design time, then audited.
128
+ - `examples/owasp-vuln-triage-and-fix.md` — an OWASP-class vulnerability triaged and remediated.
@@ -0,0 +1,66 @@
1
+ # Walkthrough: triaging and fixing an OWASP-class vulnerability
2
+
3
+ Not every task starts at design time. This shows the pack applied to a vulnerability found in
4
+ existing code — where `security-auditor` leads and the workflow compresses.
5
+
6
+ ## The finding
7
+
8
+ A routine `security-auditor` pass on a diff flags:
9
+
10
+ ```md
11
+ ### CRITICAL
12
+ - `api/documents.js:73` — GET /documents/:id loads the record by id and returns it with no check
13
+ that the document belongs to the requesting user. Any authenticated user can read any document by
14
+ guessing/enumerating ids. (A01 Broken Access Control — IDOR)
15
+ ```
16
+
17
+ Intermittent guessing isn't needed — the ids are sequential integers, so this is trivially
18
+ enumerable. CRITICAL is correct.
19
+
20
+ ## Classify (`owasp-top10`)
21
+
22
+ This is **A01 Broken Access Control**, the IDOR variant: the endpoint authenticates ("are you logged
23
+ in?") but does not authorize ("is this *your* document?"). The reference is clear — authorization must
24
+ be checked server-side against the resource owner on every request, deny-by-default.
25
+
26
+ ## Reproduce as a failing test first (`security-auditor` direction)
27
+
28
+ Before touching the handler, lock the bug down as a regression test:
29
+
30
+ ```text
31
+ ✗ user B requesting user A's document id receives 404, not 200 + the document
32
+ ```
33
+
34
+ It fails against current code — confirming the vulnerability and giving the fix a target. (404, not
35
+ 403, so the endpoint doesn't confirm the id exists — avoids the enumeration side channel too.)
36
+
37
+ ## Fix the access-control check
38
+
39
+ Add the ownership guard *before* the record is returned:
40
+
41
+ ```text
42
+ const doc = await Documents.findById(id);
43
+ if (!doc || doc.ownerId !== session.userId) return res.status(404).end();
44
+ return res.json({ data: doc, error: null });
45
+ ```
46
+
47
+ The check runs for every request and denies by default. Test goes green. A grep for the same pattern
48
+ across sibling routes (`/documents/:id/download`, `/documents/:id/versions`) confirms whether the same
49
+ gap exists elsewhere — IDOR is rarely a single-endpoint problem.
50
+
51
+ ## Re-audit + scan (`security-auditor` + `vuln-scanner`)
52
+
53
+ - `security-auditor` re-reviews: the guard is present on all three routes, the 404 leaks nothing, no
54
+ new finding introduced.
55
+ - `vuln-scanner` runs on the branch: dependencies clean, no secret introduced by the change.
56
+
57
+ ## Ship
58
+
59
+ CRITICAL closed, sibling routes patched, regression tests in place, scan clean. The fix ships.
60
+
61
+ ## The lesson
62
+
63
+ The pack turned a single flagged line into a *class* fix: classify the vulnerability (A01/IDOR), lock
64
+ it with a failing test, apply the deny-by-default mitigation, then sweep for the same pattern
65
+ elsewhere. Fixing only line 73 would have left the download and versions routes exploitable — the
66
+ OWASP framing is what prompts checking the whole class, not just the reported instance.
@@ -0,0 +1,77 @@
1
+ # Walkthrough: threat-modeling "password reset via email", then auditing it
2
+
3
+ A complete run through the five stages for a classic high-risk feature, showing which pack item leads
4
+ at each point.
5
+
6
+ ## Stage 1 — Model (`threat-modeler` + `owasp-top10`)
7
+
8
+ Trust boundaries: browser → API → email provider → token store. Walking STRIDE surfaces:
9
+
10
+ ```text
11
+ - Token guessability → tokens must be cryptographically random (A07)
12
+ - Token reuse / replay → single-use, short TTL, invalidated on use (A07)
13
+ - Host-header poisoning → reset link host must not come from the request header (A04)
14
+ - User enumeration → "email sent" response identical for known/unknown emails (A01)
15
+ - No rate limit → attacker can brute-force or spam resets (A04/A07)
16
+ ```
17
+
18
+ Each threat is tagged with its OWASP class and a planned mitigation. This list becomes the audit
19
+ agenda.
20
+
21
+ ## Stage 2 — Build (`secure-auth` + `secrets-management`)
22
+
23
+ Implement against the vetted construction:
24
+
25
+ - Reset token = 256-bit CSPRNG value, stored **hashed** at rest, single-use, 15-minute TTL.
26
+ - The "we sent you an email" response is byte-identical whether or not the account exists.
27
+ - The reset link's host is built from server config, not the incoming `Host` header.
28
+ - The email-provider API key is loaded from the secret store via `secrets-management` — it never
29
+ appears in source or committed config.
30
+
31
+ Abuse-case tests are written alongside:
32
+
33
+ ```text
34
+ ✗ a used reset token cannot be redeemed a second time
35
+ ✗ /forgot-password returns the same body for known and unknown emails
36
+ ✗ /forgot-password returns 429 after 5 attempts in the window
37
+ ```
38
+
39
+ Implement until green.
40
+
41
+ ## Stage 3 — Audit (`security-auditor` + `owasp-top10`)
42
+
43
+ The auditor reviews the diff against the stage-1 list:
44
+
45
+ ```md
46
+ ### HIGH
47
+ - `auth/reset.js:41` — token compared with `===`; use a constant-time compare to avoid a timing
48
+ oracle on the token value.
49
+ - `auth/reset.js:12` — no rate limiter on the request-reset endpoint (threat listed in the model,
50
+ not yet implemented). Add per-IP + per-account throttling.
51
+
52
+ ### LOW
53
+ - `auth/reset.js:58` — TTL is 60 min; model called for 15. Tighten.
54
+ ```
55
+
56
+ The timing-compare and rate-limit findings are fixed and re-reviewed. No CRITICAL/HIGH remains.
57
+
58
+ ## Stage 4 — Scan (`vuln-scanner`)
59
+
60
+ ```text
61
+ CVE: email-sdk@2.3.0 → transitive dep `node-fetch@2.6.1` has a known SSRF advisory.
62
+ Fix: bump to email-sdk@2.4.1 (pulls patched fetch).
63
+ Secrets: none in tree; git history clean.
64
+ ```
65
+
66
+ Dependency bumped; scan re-run clean.
67
+
68
+ ## Stage 5 — Ship
69
+
70
+ Audit clean, scan clean, no committed secret. The feature ships with its abuse-case tests as
71
+ permanent regression guards.
72
+
73
+ ## The lesson
74
+
75
+ The threat model did the heavy lifting: it predicted the rate-limit gap and the enumeration risk
76
+ *before* code existed, and it gave the auditor a precise agenda. Without it, stage 3 would have been a
77
+ generic pass that could easily have missed the missing rate limiter.