@vibecheckai/cli 3.5.0 → 3.5.2

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 (224) hide show
  1. package/bin/registry.js +214 -237
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/analyzer.js +52 -1
  4. package/bin/runners/context/generators/cursor.js +2 -49
  5. package/bin/runners/context/git-context.js +3 -1
  6. package/bin/runners/context/team-conventions.js +33 -7
  7. package/bin/runners/lib/analysis-core.js +25 -5
  8. package/bin/runners/lib/analyzers.js +431 -481
  9. package/bin/runners/lib/default-config.js +127 -0
  10. package/bin/runners/lib/doctor/modules/security.js +3 -1
  11. package/bin/runners/lib/engine/ast-cache.js +210 -0
  12. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  13. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  14. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  15. package/bin/runners/lib/engine/env-extractor.js +207 -0
  16. package/bin/runners/lib/engine/express-extractor.js +208 -0
  17. package/bin/runners/lib/engine/extractors.js +849 -0
  18. package/bin/runners/lib/engine/index.js +207 -0
  19. package/bin/runners/lib/engine/repo-index.js +514 -0
  20. package/bin/runners/lib/engine/types.js +124 -0
  21. package/bin/runners/lib/engines/accessibility-engine.js +18 -218
  22. package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
  23. package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
  24. package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
  25. package/bin/runners/lib/engines/mock-data-engine.js +10 -53
  26. package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
  27. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
  28. package/bin/runners/lib/engines/type-aware-engine.js +39 -263
  29. package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
  30. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  31. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  32. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  33. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  34. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  35. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  36. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  37. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
  38. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  39. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  40. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  41. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  42. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  43. package/bin/runners/lib/entitlements-v2.js +73 -97
  44. package/bin/runners/lib/error-handler.js +44 -3
  45. package/bin/runners/lib/error-messages.js +289 -0
  46. package/bin/runners/lib/evidence-pack.js +7 -1
  47. package/bin/runners/lib/finding-id.js +69 -0
  48. package/bin/runners/lib/finding-sorter.js +89 -0
  49. package/bin/runners/lib/html-proof-report.js +700 -350
  50. package/bin/runners/lib/missions/plan.js +6 -46
  51. package/bin/runners/lib/missions/templates.js +0 -232
  52. package/bin/runners/lib/next-action.js +560 -0
  53. package/bin/runners/lib/prerequisites.js +149 -0
  54. package/bin/runners/lib/route-detection.js +137 -68
  55. package/bin/runners/lib/scan-output.js +91 -76
  56. package/bin/runners/lib/scan-runner.js +135 -0
  57. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  58. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  59. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  60. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  61. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  62. package/bin/runners/lib/schemas/validator.js +27 -0
  63. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  64. package/bin/runners/lib/ship-output-enterprise.js +23 -23
  65. package/bin/runners/lib/ship-output.js +75 -31
  66. package/bin/runners/lib/terminal-ui.js +6 -113
  67. package/bin/runners/lib/truth.js +351 -10
  68. package/bin/runners/lib/unified-cli-output.js +430 -603
  69. package/bin/runners/lib/unified-output.js +13 -9
  70. package/bin/runners/runAIAgent.js +10 -5
  71. package/bin/runners/runAgent.js +0 -3
  72. package/bin/runners/runAllowlist.js +389 -0
  73. package/bin/runners/runApprove.js +0 -33
  74. package/bin/runners/runAuth.js +73 -45
  75. package/bin/runners/runCheckpoint.js +51 -11
  76. package/bin/runners/runClassify.js +85 -21
  77. package/bin/runners/runContext.js +0 -3
  78. package/bin/runners/runDoctor.js +41 -28
  79. package/bin/runners/runEvidencePack.js +362 -0
  80. package/bin/runners/runFirewall.js +0 -3
  81. package/bin/runners/runFirewallHook.js +0 -3
  82. package/bin/runners/runFix.js +66 -76
  83. package/bin/runners/runGuard.js +18 -411
  84. package/bin/runners/runInit.js +113 -30
  85. package/bin/runners/runLabs.js +424 -0
  86. package/bin/runners/runMcp.js +19 -25
  87. package/bin/runners/runPolish.js +64 -240
  88. package/bin/runners/runPromptFirewall.js +12 -5
  89. package/bin/runners/runProve.js +57 -22
  90. package/bin/runners/runQuickstart.js +531 -0
  91. package/bin/runners/runReality.js +59 -68
  92. package/bin/runners/runReport.js +38 -33
  93. package/bin/runners/runRuntime.js +8 -5
  94. package/bin/runners/runScan.js +1413 -190
  95. package/bin/runners/runShip.js +113 -719
  96. package/bin/runners/runTruth.js +0 -3
  97. package/bin/runners/runValidate.js +13 -9
  98. package/bin/runners/runWatch.js +23 -14
  99. package/bin/scan.js +6 -1
  100. package/bin/vibecheck.js +204 -185
  101. package/mcp-server/deprecation-middleware.js +282 -0
  102. package/mcp-server/handlers/index.ts +15 -0
  103. package/mcp-server/handlers/tool-handler.ts +554 -0
  104. package/mcp-server/index-v1.js +698 -0
  105. package/mcp-server/index.js +210 -238
  106. package/mcp-server/lib/cache-wrapper.cjs +383 -0
  107. package/mcp-server/lib/error-envelope.js +138 -0
  108. package/mcp-server/lib/executor.ts +499 -0
  109. package/mcp-server/lib/index.ts +19 -0
  110. package/mcp-server/lib/rate-limiter.js +166 -0
  111. package/mcp-server/lib/sandbox.test.ts +519 -0
  112. package/mcp-server/lib/sandbox.ts +395 -0
  113. package/mcp-server/lib/types.ts +267 -0
  114. package/mcp-server/package.json +12 -3
  115. package/mcp-server/registry/tool-registry.js +794 -0
  116. package/mcp-server/registry/tools.json +605 -0
  117. package/mcp-server/registry.test.ts +334 -0
  118. package/mcp-server/tests/tier-gating.test.js +297 -0
  119. package/mcp-server/tier-auth.js +378 -45
  120. package/mcp-server/tools-v3.js +353 -442
  121. package/mcp-server/tsconfig.json +37 -0
  122. package/mcp-server/vibecheck-2.0-tools.js +14 -1
  123. package/package.json +1 -1
  124. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  125. package/bin/runners/lib/audit-logger.js +0 -532
  126. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  127. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  128. package/bin/runners/lib/authority/authorities/human.js +0 -343
  129. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  130. package/bin/runners/lib/authority/authorities/security.js +0 -228
  131. package/bin/runners/lib/authority/index.js +0 -293
  132. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  133. package/bin/runners/lib/cli-charts.js +0 -368
  134. package/bin/runners/lib/cli-config-display.js +0 -405
  135. package/bin/runners/lib/cli-demo.js +0 -275
  136. package/bin/runners/lib/cli-errors.js +0 -438
  137. package/bin/runners/lib/cli-help-formatter.js +0 -439
  138. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  139. package/bin/runners/lib/cli-prompts.js +0 -441
  140. package/bin/runners/lib/cli-scan-cards.js +0 -362
  141. package/bin/runners/lib/compliance-reporter.js +0 -710
  142. package/bin/runners/lib/conductor/index.js +0 -671
  143. package/bin/runners/lib/easy/README.md +0 -123
  144. package/bin/runners/lib/easy/index.js +0 -140
  145. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  146. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  147. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  148. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  149. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  150. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  151. package/bin/runners/lib/engines/context-detection.js +0 -264
  152. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  153. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  154. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  155. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  156. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  157. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  158. package/bin/runners/lib/engines/framework-detection.js +0 -508
  159. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  160. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  161. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  162. package/bin/runners/lib/engines/orchestrator.js +0 -334
  163. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  167. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  168. package/bin/runners/lib/enhanced-features/index.js +0 -305
  169. package/bin/runners/lib/enhanced-output.js +0 -631
  170. package/bin/runners/lib/enterprise.js +0 -300
  171. package/bin/runners/lib/firewall/command-validator.js +0 -351
  172. package/bin/runners/lib/firewall/config.js +0 -341
  173. package/bin/runners/lib/firewall/content-validator.js +0 -519
  174. package/bin/runners/lib/firewall/index.js +0 -101
  175. package/bin/runners/lib/firewall/path-validator.js +0 -256
  176. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  177. package/bin/runners/lib/mcp-utils.js +0 -425
  178. package/bin/runners/lib/output/index.js +0 -1022
  179. package/bin/runners/lib/policy-engine.js +0 -652
  180. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  181. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  182. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  183. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  184. package/bin/runners/lib/polish/autofix/index.js +0 -200
  185. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  186. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  187. package/bin/runners/lib/polish/backend-checks.js +0 -148
  188. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  189. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  190. package/bin/runners/lib/polish/index.js +0 -71
  191. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  192. package/bin/runners/lib/polish/library-detection.js +0 -175
  193. package/bin/runners/lib/polish/performance-checks.js +0 -100
  194. package/bin/runners/lib/polish/security-checks.js +0 -148
  195. package/bin/runners/lib/polish/utils.js +0 -203
  196. package/bin/runners/lib/prompt-builder.js +0 -540
  197. package/bin/runners/lib/proof-certificate.js +0 -634
  198. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  199. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  200. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  201. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  202. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  203. package/bin/runners/lib/reality/visual-regression.js +0 -852
  204. package/bin/runners/lib/reality-profiler.js +0 -717
  205. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  206. package/bin/runners/lib/review/ai-code-review.js +0 -832
  207. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  208. package/bin/runners/lib/sbom-generator.js +0 -641
  209. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  210. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  211. package/bin/runners/lib/validators/contract-validator.js +0 -283
  212. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  213. package/bin/runners/lib/validators/dep-audit.js +0 -245
  214. package/bin/runners/lib/validators/env-validator.js +0 -319
  215. package/bin/runners/lib/validators/index.js +0 -120
  216. package/bin/runners/lib/validators/license-checker.js +0 -252
  217. package/bin/runners/lib/validators/route-validator.js +0 -290
  218. package/bin/runners/runAuthority.js +0 -528
  219. package/bin/runners/runConductor.js +0 -772
  220. package/bin/runners/runContainer.js +0 -366
  221. package/bin/runners/runEasy.js +0 -410
  222. package/bin/runners/runIaC.js +0 -372
  223. package/bin/runners/runVibe.js +0 -791
  224. package/mcp-server/tools.js +0 -495
@@ -1,1404 +0,0 @@
1
- /**
2
- * AI-Powered Scenario Generation Engine
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * COMPETITIVE MOAT FEATURE - Automatic Edge Case Test Generation
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- *
8
- * This engine analyzes the truthpack and generates intelligent test scenarios
9
- * that no human would think to test. It catches issues before they become bugs.
10
- *
11
- * Scenario Categories:
12
- * - Auth boundary testing (role-based access, session expiry, token manipulation)
13
- * - Form submission edge cases (empty, max length, special chars, XSS payloads)
14
- * - API stress scenarios (concurrent requests, race conditions, retry storms)
15
- * - Business logic edge cases (checkout abandonment, partial state, rollbacks)
16
- * - Error recovery scenarios (network failures, timeouts, partial responses)
17
- * - State machine violations (skip steps, double submit, back button attacks)
18
- */
19
-
20
- "use strict";
21
-
22
- const fs = require("fs");
23
- const path = require("path");
24
-
25
- // ═══════════════════════════════════════════════════════════════════════════════
26
- // SCENARIO TEMPLATES
27
- // ═══════════════════════════════════════════════════════════════════════════════
28
-
29
- const SCENARIO_TEMPLATES = {
30
- // ─────────────────────────────────────────────────────────────────────────────
31
- // AUTHENTICATION BOUNDARY SCENARIOS
32
- // ─────────────────────────────────────────────────────────────────────────────
33
- auth: {
34
- sessionExpiry: {
35
- name: "Session Expiry During Action",
36
- description: "Start authenticated, let session expire mid-flow, verify graceful handling",
37
- severity: "BLOCK",
38
- steps: [
39
- { action: "login", params: { role: "user" } },
40
- { action: "navigate", params: { to: "{{protectedRoute}}" } },
41
- { action: "clearCookies", params: { filter: "session" } },
42
- { action: "click", params: { selector: "{{submitButton}}" } },
43
- { action: "expect", params: { redirect: "/login", or: { modal: "session-expired" } } }
44
- ]
45
- },
46
- tokenTampering: {
47
- name: "JWT Token Manipulation",
48
- description: "Modify JWT payload to test server-side validation",
49
- severity: "BLOCK",
50
- steps: [
51
- { action: "login", params: { role: "user" } },
52
- { action: "modifyToken", params: { field: "role", value: "admin" } },
53
- { action: "navigate", params: { to: "{{adminRoute}}" } },
54
- { action: "expect", params: { status: 403, or: { redirect: "/unauthorized" } } }
55
- ]
56
- },
57
- roleEscalation: {
58
- name: "Horizontal Privilege Escalation",
59
- description: "Access another user's resources by ID manipulation",
60
- severity: "BLOCK",
61
- steps: [
62
- { action: "login", params: { role: "user", id: "user-1" } },
63
- { action: "navigate", params: { to: "{{resourceRoute}}", params: { id: "user-2-resource" } } },
64
- { action: "expect", params: { status: 403, or: { empty: true } } }
65
- ]
66
- },
67
- concurrentSessions: {
68
- name: "Concurrent Session Handling",
69
- description: "Test behavior when same user logs in from multiple sessions",
70
- severity: "WARN",
71
- steps: [
72
- { action: "login", params: { role: "user", session: "A" } },
73
- { action: "login", params: { role: "user", session: "B" } },
74
- { action: "switchSession", params: { to: "A" } },
75
- { action: "navigate", params: { to: "{{protectedRoute}}" } },
76
- { action: "expect", params: { valid: true, or: { invalidated: true } } }
77
- ]
78
- }
79
- },
80
-
81
- // ─────────────────────────────────────────────────────────────────────────────
82
- // FORM SUBMISSION EDGE CASES
83
- // ─────────────────────────────────────────────────────────────────────────────
84
- forms: {
85
- emptySubmit: {
86
- name: "Empty Form Submission",
87
- description: "Submit form with all fields empty",
88
- severity: "WARN",
89
- steps: [
90
- { action: "navigate", params: { to: "{{formRoute}}" } },
91
- { action: "clearAll", params: { selector: "form input" } },
92
- { action: "click", params: { selector: "{{submitButton}}" } },
93
- { action: "expect", params: { validation: true, noServerError: true } }
94
- ]
95
- },
96
- maxLength: {
97
- name: "Maximum Length Boundary",
98
- description: "Submit fields at and beyond maximum length",
99
- severity: "WARN",
100
- steps: [
101
- { action: "navigate", params: { to: "{{formRoute}}" } },
102
- { action: "fill", params: { selector: "{{textField}}", value: "A".repeat(10001) } },
103
- { action: "click", params: { selector: "{{submitButton}}" } },
104
- { action: "expect", params: { truncated: true, or: { validation: "max length" } } }
105
- ]
106
- },
107
- xssPayloads: {
108
- name: "XSS Payload Injection",
109
- description: "Inject script tags and event handlers",
110
- severity: "BLOCK",
111
- payloads: [
112
- "<script>alert('xss')</script>",
113
- "<img src=x onerror=alert('xss')>",
114
- "javascript:alert('xss')",
115
- "{{constructor.constructor('alert(1)')()}}"
116
- ],
117
- steps: [
118
- { action: "navigate", params: { to: "{{formRoute}}" } },
119
- { action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
120
- { action: "click", params: { selector: "{{submitButton}}" } },
121
- { action: "expect", params: { sanitized: true, noExecution: true } }
122
- ]
123
- },
124
- sqlInjection: {
125
- name: "SQL Injection Patterns",
126
- description: "Test SQL injection in form fields",
127
- severity: "BLOCK",
128
- payloads: [
129
- "'; DROP TABLE users; --",
130
- "1' OR '1'='1",
131
- "UNION SELECT * FROM users",
132
- "admin'--"
133
- ],
134
- steps: [
135
- { action: "navigate", params: { to: "{{formRoute}}" } },
136
- { action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
137
- { action: "click", params: { selector: "{{submitButton}}" } },
138
- { action: "expect", params: { escaped: true, noDbError: true } }
139
- ]
140
- },
141
- unicodeBomb: {
142
- name: "Unicode and Special Characters",
143
- description: "Test handling of unicode, emojis, RTL, zero-width chars",
144
- severity: "WARN",
145
- payloads: [
146
- "🔥💀👾🎮", // Emojis
147
- "مرحبا بالعالم", // Arabic RTL
148
- "test\u0000null", // Null byte
149
- "test\u200Bhidden", // Zero-width space
150
- "Ṱ̈́͑ḛ̏̀s̞̿t̰̊̏", // Zalgo text
151
- "test\r\ninjection" // CRLF
152
- ],
153
- steps: [
154
- { action: "navigate", params: { to: "{{formRoute}}" } },
155
- { action: "fill", params: { selector: "{{textField}}", value: "{{payload}}" } },
156
- { action: "click", params: { selector: "{{submitButton}}" } },
157
- { action: "expect", params: { handled: true, noCorruption: true } }
158
- ]
159
- },
160
- doubleSubmit: {
161
- name: "Double Submit Prevention",
162
- description: "Rapidly click submit twice to test idempotency",
163
- severity: "BLOCK",
164
- steps: [
165
- { action: "navigate", params: { to: "{{formRoute}}" } },
166
- { action: "fillValid", params: { form: "{{formSelector}}" } },
167
- { action: "doubleClick", params: { selector: "{{submitButton}}", delay: 50 } },
168
- { action: "expect", params: { singleSubmission: true, or: { buttonDisabled: true } } }
169
- ]
170
- }
171
- },
172
-
173
- // ─────────────────────────────────────────────────────────────────────────────
174
- // API STRESS SCENARIOS
175
- // ─────────────────────────────────────────────────────────────────────────────
176
- api: {
177
- raceCondition: {
178
- name: "Race Condition Detection",
179
- description: "Concurrent identical requests to detect race conditions",
180
- severity: "BLOCK",
181
- steps: [
182
- { action: "login", params: { role: "user" } },
183
- { action: "parallel", params: {
184
- count: 5,
185
- action: "apiCall",
186
- endpoint: "{{criticalEndpoint}}",
187
- method: "POST",
188
- body: "{{validPayload}}"
189
- }},
190
- { action: "expect", params: {
191
- idempotent: true,
192
- or: { exactlyOne: true },
193
- noDataCorruption: true
194
- }}
195
- ]
196
- },
197
- retryStorm: {
198
- name: "Retry Storm Handling",
199
- description: "Simulate client retry behavior on timeout",
200
- severity: "WARN",
201
- steps: [
202
- { action: "interceptNetwork", params: { pattern: "{{apiPattern}}", delay: 10000 } },
203
- { action: "navigate", params: { to: "{{pageWithApi}}" } },
204
- { action: "wait", params: { ms: 2000 } },
205
- { action: "releaseNetwork" },
206
- { action: "expect", params: { noMultipleRequests: true, gracefulTimeout: true } }
207
- ]
208
- },
209
- rateLimiting: {
210
- name: "Rate Limit Behavior",
211
- description: "Verify rate limiting is enforced and user-friendly",
212
- severity: "WARN",
213
- steps: [
214
- { action: "loop", params: { count: 100, delay: 10 }, action: {
215
- action: "apiCall",
216
- endpoint: "{{rateLimitedEndpoint}}",
217
- method: "GET"
218
- }},
219
- { action: "expect", params: {
220
- rateLimited: true,
221
- status: 429,
222
- retryAfter: "present",
223
- userMessage: "present"
224
- }}
225
- ]
226
- }
227
- },
228
-
229
- // ─────────────────────────────────────────────────────────────────────────────
230
- // BUSINESS LOGIC EDGE CASES
231
- // ─────────────────────────────────────────────────────────────────────────────
232
- business: {
233
- checkoutAbandonment: {
234
- name: "Checkout Flow Abandonment",
235
- description: "Start checkout, abandon, return - verify cart state",
236
- severity: "WARN",
237
- steps: [
238
- { action: "addToCart", params: { product: "{{testProduct}}" } },
239
- { action: "navigate", params: { to: "/checkout" } },
240
- { action: "fillStep", params: { step: "shipping" } },
241
- { action: "navigate", params: { to: "/" } },
242
- { action: "wait", params: { ms: 5000 } },
243
- { action: "navigate", params: { to: "/checkout" } },
244
- { action: "expect", params: { cartPreserved: true, stepPreserved: true } }
245
- ]
246
- },
247
- priceManipulation: {
248
- name: "Price Manipulation Attack",
249
- description: "Attempt to modify prices client-side",
250
- severity: "BLOCK",
251
- steps: [
252
- { action: "addToCart", params: { product: "{{testProduct}}" } },
253
- { action: "interceptRequest", params: {
254
- pattern: "/api/checkout",
255
- modify: { "items[0].price": 0.01 }
256
- }},
257
- { action: "submitCheckout" },
258
- { action: "expect", params: {
259
- serverValidation: true,
260
- priceFromServer: true,
261
- noDiscount: true
262
- }}
263
- ]
264
- },
265
- inventoryRace: {
266
- name: "Inventory Race Condition",
267
- description: "Two users checkout last item simultaneously",
268
- severity: "BLOCK",
269
- steps: [
270
- { action: "setInventory", params: { product: "{{testProduct}}", count: 1 } },
271
- { action: "parallel", params: { sessions: ["A", "B"] }, steps: [
272
- { action: "addToCart", params: { product: "{{testProduct}}" } },
273
- { action: "submitCheckout" }
274
- ]},
275
- { action: "expect", params: {
276
- exactlyOneSuccess: true,
277
- otherFails: "out of stock",
278
- noOversell: true
279
- }}
280
- ]
281
- },
282
- negativeQuantity: {
283
- name: "Negative Quantity Attack",
284
- description: "Attempt to add negative quantities",
285
- severity: "BLOCK",
286
- steps: [
287
- { action: "interceptRequest", params: {
288
- pattern: "/api/cart",
289
- modify: { quantity: -5 }
290
- }},
291
- { action: "expect", params: { rejected: true, noCredit: true } }
292
- ]
293
- }
294
- },
295
-
296
- // ─────────────────────────────────────────────────────────────────────────────
297
- // ERROR RECOVERY SCENARIOS
298
- // ─────────────────────────────────────────────────────────────────────────────
299
- errors: {
300
- networkDrop: {
301
- name: "Network Drop Mid-Action",
302
- description: "Cut network during API call, verify recovery",
303
- severity: "WARN",
304
- steps: [
305
- { action: "navigate", params: { to: "{{pageWithForm}}" } },
306
- { action: "fillValid", params: { form: "{{formSelector}}" } },
307
- { action: "goOffline" },
308
- { action: "click", params: { selector: "{{submitButton}}" } },
309
- { action: "expect", params: {
310
- offlineMessage: true,
311
- dataNotLost: true,
312
- retryOption: true
313
- }},
314
- { action: "goOnline" },
315
- { action: "click", params: { selector: "{{retryButton}}" } },
316
- { action: "expect", params: { success: true } }
317
- ]
318
- },
319
- partialResponse: {
320
- name: "Partial Response Handling",
321
- description: "Server returns truncated JSON",
322
- severity: "WARN",
323
- steps: [
324
- { action: "interceptResponse", params: {
325
- pattern: "{{apiPattern}}",
326
- truncate: 0.5 // Cut response at 50%
327
- }},
328
- { action: "navigate", params: { to: "{{pageWithApi}}" } },
329
- { action: "expect", params: {
330
- gracefulError: true,
331
- noJsCrash: true,
332
- retryOption: true
333
- }}
334
- ]
335
- },
336
- serverError: {
337
- name: "500 Error Recovery",
338
- description: "Server returns 500, verify user-friendly handling",
339
- severity: "WARN",
340
- steps: [
341
- { action: "interceptResponse", params: {
342
- pattern: "{{apiPattern}}",
343
- status: 500,
344
- body: "Internal Server Error"
345
- }},
346
- { action: "navigate", params: { to: "{{pageWithApi}}" } },
347
- { action: "expect", params: {
348
- userFriendlyError: true,
349
- noStackTrace: true,
350
- reportOption: true
351
- }}
352
- ]
353
- }
354
- },
355
-
356
- // ─────────────────────────────────────────────────────────────────────────────
357
- // STATE MACHINE VIOLATIONS
358
- // ─────────────────────────────────────────────────────────────────────────────
359
- stateMachine: {
360
- skipStep: {
361
- name: "Wizard Step Skip Attack",
362
- description: "Try to skip steps in multi-step flow",
363
- severity: "BLOCK",
364
- steps: [
365
- { action: "navigate", params: { to: "{{wizardStep1}}" } },
366
- { action: "navigate", params: { to: "{{wizardStep3}}" } }, // Skip step 2
367
- { action: "expect", params: {
368
- redirectToStep: 2,
369
- or: { blocked: true },
370
- noIncompleteSubmit: true
371
- }}
372
- ]
373
- },
374
- backButtonAbuse: {
375
- name: "Back Button State Corruption",
376
- description: "Use back button after completing flow",
377
- severity: "WARN",
378
- steps: [
379
- { action: "completeFlow", params: { flow: "{{checkoutFlow}}" } },
380
- { action: "goBack" },
381
- { action: "goBack" },
382
- { action: "click", params: { selector: "{{submitButton}}" } },
383
- { action: "expect", params: {
384
- noDuplicate: true,
385
- or: { alreadyCompleted: true }
386
- }}
387
- ]
388
- },
389
- directUrlAccess: {
390
- name: "Direct URL to Protected State",
391
- description: "Access completion page without going through flow",
392
- severity: "BLOCK",
393
- steps: [
394
- { action: "navigate", params: { to: "{{confirmationPage}}" } },
395
- { action: "expect", params: {
396
- redirect: "{{startPage}}",
397
- or: { error: "invalid state" },
398
- noFakeSuccess: true
399
- }}
400
- ]
401
- }
402
- },
403
-
404
- // ─────────────────────────────────────────────────────────────────────────────
405
- // PERFORMANCE & RESOURCE ABUSE
406
- // ─────────────────────────────────────────────────────────────────────────────
407
- performance: {
408
- largeFileUpload: {
409
- name: "Oversized File Upload",
410
- description: "Upload file exceeding limit",
411
- severity: "WARN",
412
- steps: [
413
- { action: "navigate", params: { to: "{{uploadPage}}" } },
414
- { action: "uploadFile", params: { size: "100MB", type: "image/png" } },
415
- { action: "expect", params: {
416
- clientValidation: true,
417
- or: { serverReject: true },
418
- noHang: true,
419
- maxWait: 5000
420
- }}
421
- ]
422
- },
423
- infiniteScroll: {
424
- name: "Infinite Scroll Memory Leak",
425
- description: "Scroll to load many pages, check memory",
426
- severity: "WARN",
427
- steps: [
428
- { action: "navigate", params: { to: "{{infiniteScrollPage}}" } },
429
- { action: "recordMemory", params: { label: "start" } },
430
- { action: "scrollToBottom", params: { times: 50 } },
431
- { action: "recordMemory", params: { label: "end" } },
432
- { action: "expect", params: {
433
- memoryGrowth: { max: "3x" },
434
- noFreeze: true
435
- }}
436
- ]
437
- },
438
- fileTypeSpoofing: {
439
- name: "File Type Spoofing",
440
- description: "Upload executable with image extension",
441
- severity: "BLOCK",
442
- steps: [
443
- { action: "navigate", params: { to: "{{uploadPage}}" } },
444
- { action: "uploadSpoofedFile", params: {
445
- realType: "application/x-executable",
446
- fakeExtension: ".png",
447
- fakeMime: "image/png"
448
- }},
449
- { action: "expect", params: {
450
- rejected: true,
451
- magicByteCheck: true
452
- }}
453
- ]
454
- }
455
- }
456
- };
457
-
458
- // ═══════════════════════════════════════════════════════════════════════════════
459
- // SCENARIO GENERATOR CLASS
460
- // ═══════════════════════════════════════════════════════════════════════════════
461
-
462
- class ScenarioGenerator {
463
- constructor(options = {}) {
464
- this.truthpackPath = options.truthpackPath || ".vibecheck/truth/truthpack.json";
465
- this.maxScenariosPerCategory = options.maxScenariosPerCategory || 5;
466
- this.severityFilter = options.severityFilter || ["BLOCK", "WARN"];
467
- this.categories = options.categories || Object.keys(SCENARIO_TEMPLATES);
468
- this.customScenarios = options.customScenarios || [];
469
- }
470
-
471
- /**
472
- * Load and analyze the truthpack to understand the app
473
- */
474
- loadTruthpack(projectRoot) {
475
- const truthpackFile = path.join(projectRoot, this.truthpackPath);
476
-
477
- if (!fs.existsSync(truthpackFile)) {
478
- return null;
479
- }
480
-
481
- try {
482
- return JSON.parse(fs.readFileSync(truthpackFile, "utf8"));
483
- } catch {
484
- return null;
485
- }
486
- }
487
-
488
- /**
489
- * Extract testable features from truthpack
490
- */
491
- analyzeAppStructure(truthpack) {
492
- const analysis = {
493
- routes: {
494
- public: [],
495
- protected: [],
496
- admin: [],
497
- api: [],
498
- forms: [],
499
- uploads: [],
500
- checkout: [],
501
- wizard: []
502
- },
503
- auth: {
504
- hasAuth: false,
505
- roles: [],
506
- sessionType: null,
507
- protectedPatterns: []
508
- },
509
- forms: [],
510
- apis: {
511
- endpoints: [],
512
- criticalEndpoints: [],
513
- rateLimited: []
514
- },
515
- features: {
516
- hasCheckout: false,
517
- hasUpload: false,
518
- hasWizard: false,
519
- hasInfiniteScroll: false,
520
- hasRealtime: false
521
- }
522
- };
523
-
524
- if (!truthpack) return analysis;
525
-
526
- // Analyze routes
527
- if (truthpack.routes) {
528
- const routes = truthpack.routes.serverRoutes || truthpack.routes.routes || [];
529
-
530
- for (const route of routes) {
531
- const routePath = route.path || route;
532
-
533
- // Categorize routes
534
- if (/\/api\//i.test(routePath)) {
535
- analysis.routes.api.push(routePath);
536
-
537
- // Detect critical endpoints
538
- if (/checkout|payment|order|billing|transfer/i.test(routePath)) {
539
- analysis.apis.criticalEndpoints.push(routePath);
540
- analysis.features.hasCheckout = true;
541
- }
542
- }
543
-
544
- if (/admin|dashboard|manage/i.test(routePath)) {
545
- analysis.routes.admin.push(routePath);
546
- }
547
-
548
- if (/upload|file|image|document/i.test(routePath)) {
549
- analysis.routes.uploads.push(routePath);
550
- analysis.features.hasUpload = true;
551
- }
552
-
553
- if (/wizard|step|onboard/i.test(routePath)) {
554
- analysis.routes.wizard.push(routePath);
555
- analysis.features.hasWizard = true;
556
- }
557
-
558
- if (/form|submit|create|edit|new/i.test(routePath)) {
559
- analysis.routes.forms.push(routePath);
560
- }
561
- }
562
- }
563
-
564
- // Analyze auth
565
- if (truthpack.auth) {
566
- analysis.auth.hasAuth = true;
567
- analysis.auth.roles = truthpack.auth.roles || [];
568
- analysis.auth.protectedPatterns = truthpack.auth.protectedPatterns ||
569
- truthpack.auth.nextMatcherPatterns || [];
570
-
571
- // Detect session type
572
- if (truthpack.auth.jwt || truthpack.auth.tokenBased) {
573
- analysis.auth.sessionType = "jwt";
574
- } else if (truthpack.auth.sessionBased) {
575
- analysis.auth.sessionType = "session";
576
- }
577
- }
578
-
579
- // Analyze env for feature detection
580
- if (truthpack.env) {
581
- const envVars = Object.keys(truthpack.env.declared || {});
582
-
583
- if (envVars.some(v => /stripe|payment|billing/i.test(v))) {
584
- analysis.features.hasCheckout = true;
585
- }
586
-
587
- if (envVars.some(v => /s3|cloudinary|upload/i.test(v))) {
588
- analysis.features.hasUpload = true;
589
- }
590
-
591
- if (envVars.some(v => /pusher|socket|realtime/i.test(v))) {
592
- analysis.features.hasRealtime = true;
593
- }
594
- }
595
-
596
- return analysis;
597
- }
598
-
599
- /**
600
- * Generate scenarios based on app analysis
601
- */
602
- generateScenarios(projectRoot) {
603
- const truthpack = this.loadTruthpack(projectRoot);
604
- const analysis = this.analyzeAppStructure(truthpack);
605
- const scenarios = [];
606
-
607
- // Generate auth scenarios if app has auth
608
- if (analysis.auth.hasAuth && this.categories.includes("auth")) {
609
- scenarios.push(...this.generateAuthScenarios(analysis));
610
- }
611
-
612
- // Generate form scenarios if app has forms
613
- if (analysis.routes.forms.length > 0 && this.categories.includes("forms")) {
614
- scenarios.push(...this.generateFormScenarios(analysis));
615
- }
616
-
617
- // Generate API scenarios if app has APIs
618
- if (analysis.routes.api.length > 0 && this.categories.includes("api")) {
619
- scenarios.push(...this.generateApiScenarios(analysis));
620
- }
621
-
622
- // Generate business logic scenarios if app has checkout
623
- if (analysis.features.hasCheckout && this.categories.includes("business")) {
624
- scenarios.push(...this.generateBusinessScenarios(analysis));
625
- }
626
-
627
- // Generate error scenarios
628
- if (this.categories.includes("errors")) {
629
- scenarios.push(...this.generateErrorScenarios(analysis));
630
- }
631
-
632
- // Generate state machine scenarios if app has wizards
633
- if (analysis.features.hasWizard && this.categories.includes("stateMachine")) {
634
- scenarios.push(...this.generateStateMachineScenarios(analysis));
635
- }
636
-
637
- // Generate performance scenarios
638
- if (this.categories.includes("performance")) {
639
- scenarios.push(...this.generatePerformanceScenarios(analysis));
640
- }
641
-
642
- // Add custom scenarios
643
- scenarios.push(...this.customScenarios);
644
-
645
- // Filter by severity
646
- const filtered = scenarios.filter(s =>
647
- this.severityFilter.includes(s.severity)
648
- );
649
-
650
- // Deduplicate and prioritize
651
- return this.prioritizeScenarios(filtered);
652
- }
653
-
654
- /**
655
- * Generate auth-related scenarios
656
- */
657
- generateAuthScenarios(analysis) {
658
- const scenarios = [];
659
- const templates = SCENARIO_TEMPLATES.auth;
660
-
661
- // Session expiry scenario
662
- if (analysis.auth.sessionType) {
663
- const protectedRoute = analysis.routes.protected[0] ||
664
- analysis.routes.admin[0] ||
665
- "/dashboard";
666
-
667
- scenarios.push({
668
- ...templates.sessionExpiry,
669
- category: "auth",
670
- variables: {
671
- protectedRoute,
672
- submitButton: "[type='submit'], button:has-text('Save'), button:has-text('Submit')"
673
- },
674
- confidence: 0.9,
675
- generated: true
676
- });
677
- }
678
-
679
- // Token tampering scenario (for JWT auth)
680
- if (analysis.auth.sessionType === "jwt") {
681
- const adminRoute = analysis.routes.admin[0] || "/admin";
682
-
683
- scenarios.push({
684
- ...templates.tokenTampering,
685
- category: "auth",
686
- variables: { adminRoute },
687
- confidence: 0.95,
688
- generated: true
689
- });
690
- }
691
-
692
- // Role escalation scenario
693
- if (analysis.auth.roles.length > 1) {
694
- const resourceRoute = analysis.routes.api.find(r =>
695
- /user|profile|account|resource/i.test(r)
696
- ) || "/api/users/:id";
697
-
698
- scenarios.push({
699
- ...templates.roleEscalation,
700
- category: "auth",
701
- variables: { resourceRoute },
702
- confidence: 0.9,
703
- generated: true
704
- });
705
- }
706
-
707
- // Concurrent sessions
708
- scenarios.push({
709
- ...templates.concurrentSessions,
710
- category: "auth",
711
- variables: {
712
- protectedRoute: analysis.routes.protected[0] || "/dashboard"
713
- },
714
- confidence: 0.8,
715
- generated: true
716
- });
717
-
718
- return scenarios.slice(0, this.maxScenariosPerCategory);
719
- }
720
-
721
- /**
722
- * Generate form-related scenarios
723
- */
724
- generateFormScenarios(analysis) {
725
- const scenarios = [];
726
- const templates = SCENARIO_TEMPLATES.forms;
727
- const formRoutes = analysis.routes.forms;
728
-
729
- if (formRoutes.length === 0) return scenarios;
730
-
731
- const primaryForm = formRoutes[0];
732
-
733
- // Empty submit
734
- scenarios.push({
735
- ...templates.emptySubmit,
736
- category: "forms",
737
- variables: {
738
- formRoute: primaryForm,
739
- submitButton: "[type='submit']"
740
- },
741
- confidence: 0.95,
742
- generated: true
743
- });
744
-
745
- // Max length
746
- scenarios.push({
747
- ...templates.maxLength,
748
- category: "forms",
749
- variables: {
750
- formRoute: primaryForm,
751
- textField: "input[type='text'], textarea",
752
- submitButton: "[type='submit']"
753
- },
754
- confidence: 0.85,
755
- generated: true
756
- });
757
-
758
- // XSS payloads - generate one per payload
759
- for (const payload of templates.xssPayloads.payloads.slice(0, 2)) {
760
- scenarios.push({
761
- ...templates.xssPayloads,
762
- category: "forms",
763
- variables: {
764
- formRoute: primaryForm,
765
- textField: "input[type='text'], textarea",
766
- submitButton: "[type='submit']",
767
- payload
768
- },
769
- confidence: 0.95,
770
- generated: true
771
- });
772
- }
773
-
774
- // Double submit
775
- scenarios.push({
776
- ...templates.doubleSubmit,
777
- category: "forms",
778
- variables: {
779
- formRoute: primaryForm,
780
- formSelector: "form",
781
- submitButton: "[type='submit']"
782
- },
783
- confidence: 0.9,
784
- generated: true
785
- });
786
-
787
- return scenarios.slice(0, this.maxScenariosPerCategory);
788
- }
789
-
790
- /**
791
- * Generate API-related scenarios
792
- */
793
- generateApiScenarios(analysis) {
794
- const scenarios = [];
795
- const templates = SCENARIO_TEMPLATES.api;
796
-
797
- // Race condition for critical endpoints
798
- if (analysis.apis.criticalEndpoints.length > 0) {
799
- scenarios.push({
800
- ...templates.raceCondition,
801
- category: "api",
802
- variables: {
803
- criticalEndpoint: analysis.apis.criticalEndpoints[0],
804
- validPayload: "{}"
805
- },
806
- confidence: 0.9,
807
- generated: true
808
- });
809
- }
810
-
811
- // Retry storm for any API
812
- if (analysis.routes.api.length > 0) {
813
- scenarios.push({
814
- ...templates.retryStorm,
815
- category: "api",
816
- variables: {
817
- apiPattern: analysis.routes.api[0],
818
- pageWithApi: "/" // Will be refined based on route analysis
819
- },
820
- confidence: 0.8,
821
- generated: true
822
- });
823
- }
824
-
825
- // Rate limiting
826
- scenarios.push({
827
- ...templates.rateLimiting,
828
- category: "api",
829
- variables: {
830
- rateLimitedEndpoint: analysis.routes.api[0] || "/api/data"
831
- },
832
- confidence: 0.85,
833
- generated: true
834
- });
835
-
836
- return scenarios.slice(0, this.maxScenariosPerCategory);
837
- }
838
-
839
- /**
840
- * Generate business logic scenarios
841
- */
842
- generateBusinessScenarios(analysis) {
843
- const scenarios = [];
844
- const templates = SCENARIO_TEMPLATES.business;
845
-
846
- if (!analysis.features.hasCheckout) return scenarios;
847
-
848
- // Price manipulation
849
- scenarios.push({
850
- ...templates.priceManipulation,
851
- category: "business",
852
- variables: {
853
- testProduct: "test-product-id"
854
- },
855
- confidence: 0.95,
856
- generated: true
857
- });
858
-
859
- // Inventory race condition
860
- scenarios.push({
861
- ...templates.inventoryRace,
862
- category: "business",
863
- variables: {
864
- testProduct: "test-product-id"
865
- },
866
- confidence: 0.9,
867
- generated: true
868
- });
869
-
870
- // Negative quantity
871
- scenarios.push({
872
- ...templates.negativeQuantity,
873
- category: "business",
874
- variables: {},
875
- confidence: 0.95,
876
- generated: true
877
- });
878
-
879
- // Checkout abandonment
880
- scenarios.push({
881
- ...templates.checkoutAbandonment,
882
- category: "business",
883
- variables: {
884
- testProduct: "test-product-id"
885
- },
886
- confidence: 0.8,
887
- generated: true
888
- });
889
-
890
- return scenarios.slice(0, this.maxScenariosPerCategory);
891
- }
892
-
893
- /**
894
- * Generate error recovery scenarios
895
- */
896
- generateErrorScenarios(analysis) {
897
- const scenarios = [];
898
- const templates = SCENARIO_TEMPLATES.errors;
899
-
900
- const apiPattern = analysis.routes.api[0] || "/api/*";
901
- const formPage = analysis.routes.forms[0] || "/";
902
-
903
- // Network drop
904
- scenarios.push({
905
- ...templates.networkDrop,
906
- category: "errors",
907
- variables: {
908
- pageWithForm: formPage,
909
- formSelector: "form",
910
- submitButton: "[type='submit']",
911
- retryButton: "button:has-text('Retry'), button:has-text('Try Again')"
912
- },
913
- confidence: 0.85,
914
- generated: true
915
- });
916
-
917
- // Partial response
918
- scenarios.push({
919
- ...templates.partialResponse,
920
- category: "errors",
921
- variables: {
922
- apiPattern,
923
- pageWithApi: "/"
924
- },
925
- confidence: 0.8,
926
- generated: true
927
- });
928
-
929
- // 500 error
930
- scenarios.push({
931
- ...templates.serverError,
932
- category: "errors",
933
- variables: {
934
- apiPattern,
935
- pageWithApi: "/"
936
- },
937
- confidence: 0.85,
938
- generated: true
939
- });
940
-
941
- return scenarios.slice(0, this.maxScenariosPerCategory);
942
- }
943
-
944
- /**
945
- * Generate state machine scenarios
946
- */
947
- generateStateMachineScenarios(analysis) {
948
- const scenarios = [];
949
- const templates = SCENARIO_TEMPLATES.stateMachine;
950
-
951
- if (!analysis.features.hasWizard) return scenarios;
952
-
953
- const wizardRoutes = analysis.routes.wizard;
954
- if (wizardRoutes.length >= 2) {
955
- // Skip step
956
- scenarios.push({
957
- ...templates.skipStep,
958
- category: "stateMachine",
959
- variables: {
960
- wizardStep1: wizardRoutes[0],
961
- wizardStep3: wizardRoutes[2] || wizardRoutes[1]
962
- },
963
- confidence: 0.9,
964
- generated: true
965
- });
966
- }
967
-
968
- // Direct URL access
969
- scenarios.push({
970
- ...templates.directUrlAccess,
971
- category: "stateMachine",
972
- variables: {
973
- confirmationPage: "/checkout/confirmation",
974
- startPage: "/checkout"
975
- },
976
- confidence: 0.85,
977
- generated: true
978
- });
979
-
980
- // Back button abuse
981
- scenarios.push({
982
- ...templates.backButtonAbuse,
983
- category: "stateMachine",
984
- variables: {
985
- checkoutFlow: "checkout",
986
- submitButton: "[type='submit']"
987
- },
988
- confidence: 0.8,
989
- generated: true
990
- });
991
-
992
- return scenarios.slice(0, this.maxScenariosPerCategory);
993
- }
994
-
995
- /**
996
- * Generate performance scenarios
997
- */
998
- generatePerformanceScenarios(analysis) {
999
- const scenarios = [];
1000
- const templates = SCENARIO_TEMPLATES.performance;
1001
-
1002
- // Large file upload
1003
- if (analysis.features.hasUpload) {
1004
- scenarios.push({
1005
- ...templates.largeFileUpload,
1006
- category: "performance",
1007
- variables: {
1008
- uploadPage: analysis.routes.uploads[0] || "/upload"
1009
- },
1010
- confidence: 0.85,
1011
- generated: true
1012
- });
1013
-
1014
- // File type spoofing
1015
- scenarios.push({
1016
- ...templates.fileTypeSpoofing,
1017
- category: "performance",
1018
- variables: {
1019
- uploadPage: analysis.routes.uploads[0] || "/upload"
1020
- },
1021
- confidence: 0.9,
1022
- generated: true
1023
- });
1024
- }
1025
-
1026
- // Infinite scroll memory leak (if detected or assume present)
1027
- scenarios.push({
1028
- ...templates.infiniteScroll,
1029
- category: "performance",
1030
- variables: {
1031
- infiniteScrollPage: "/feed"
1032
- },
1033
- confidence: 0.7,
1034
- generated: true
1035
- });
1036
-
1037
- return scenarios.slice(0, this.maxScenariosPerCategory);
1038
- }
1039
-
1040
- /**
1041
- * Prioritize and deduplicate scenarios
1042
- */
1043
- prioritizeScenarios(scenarios) {
1044
- // Sort by: severity (BLOCK first), then confidence
1045
- const severityOrder = { BLOCK: 0, WARN: 1, INFO: 2 };
1046
-
1047
- return scenarios.sort((a, b) => {
1048
- const sevDiff = severityOrder[a.severity] - severityOrder[b.severity];
1049
- if (sevDiff !== 0) return sevDiff;
1050
- return b.confidence - a.confidence;
1051
- });
1052
- }
1053
-
1054
- /**
1055
- * Export scenarios for Reality Mode execution
1056
- */
1057
- exportForReality(scenarios) {
1058
- return scenarios.map(scenario => ({
1059
- id: `${scenario.category}-${scenario.name.toLowerCase().replace(/\s+/g, "-")}`,
1060
- name: scenario.name,
1061
- description: scenario.description,
1062
- category: scenario.category,
1063
- severity: scenario.severity,
1064
- confidence: scenario.confidence,
1065
- steps: this.resolveVariables(scenario.steps, scenario.variables),
1066
- generated: scenario.generated || false,
1067
- expectedOutcomes: this.extractExpectations(scenario.steps)
1068
- }));
1069
- }
1070
-
1071
- /**
1072
- * Resolve template variables in steps
1073
- */
1074
- resolveVariables(steps, variables) {
1075
- if (!steps || !variables) return steps;
1076
-
1077
- return JSON.parse(
1078
- JSON.stringify(steps).replace(
1079
- /\{\{(\w+)\}\}/g,
1080
- (match, varName) => variables[varName] || match
1081
- )
1082
- );
1083
- }
1084
-
1085
- /**
1086
- * Extract expected outcomes from steps
1087
- */
1088
- extractExpectations(steps) {
1089
- if (!steps) return [];
1090
-
1091
- return steps
1092
- .filter(step => step.action === "expect")
1093
- .map(step => step.params);
1094
- }
1095
- }
1096
-
1097
- // ═══════════════════════════════════════════════════════════════════════════════
1098
- // SCENARIO EXECUTOR (integrates with Playwright)
1099
- // ═══════════════════════════════════════════════════════════════════════════════
1100
-
1101
- class ScenarioExecutor {
1102
- constructor(options = {}) {
1103
- this.page = options.page;
1104
- this.context = options.context;
1105
- this.baseUrl = options.baseUrl;
1106
- this.timeout = options.timeout || 30000;
1107
- this.results = [];
1108
- }
1109
-
1110
- /**
1111
- * Execute a single scenario
1112
- */
1113
- async executeScenario(scenario) {
1114
- const result = {
1115
- id: scenario.id,
1116
- name: scenario.name,
1117
- category: scenario.category,
1118
- severity: scenario.severity,
1119
- status: "pending",
1120
- steps: [],
1121
- violations: [],
1122
- duration: 0
1123
- };
1124
-
1125
- const startTime = Date.now();
1126
-
1127
- try {
1128
- for (const step of scenario.steps) {
1129
- const stepResult = await this.executeStep(step);
1130
- result.steps.push(stepResult);
1131
-
1132
- if (stepResult.status === "failed" && step.action === "expect") {
1133
- result.violations.push({
1134
- step: step.action,
1135
- expected: step.params,
1136
- actual: stepResult.actual,
1137
- message: stepResult.error
1138
- });
1139
- }
1140
- }
1141
-
1142
- result.status = result.violations.length > 0 ? "failed" : "passed";
1143
- } catch (error) {
1144
- result.status = "error";
1145
- result.error = error.message;
1146
- }
1147
-
1148
- result.duration = Date.now() - startTime;
1149
- this.results.push(result);
1150
-
1151
- return result;
1152
- }
1153
-
1154
- /**
1155
- * Execute a single step
1156
- */
1157
- async executeStep(step) {
1158
- const stepResult = {
1159
- action: step.action,
1160
- params: step.params,
1161
- status: "pending",
1162
- timestamp: Date.now()
1163
- };
1164
-
1165
- try {
1166
- switch (step.action) {
1167
- case "navigate":
1168
- await this.page.goto(this.resolveUrl(step.params.to));
1169
- stepResult.status = "passed";
1170
- break;
1171
-
1172
- case "click":
1173
- await this.page.click(step.params.selector, { timeout: this.timeout });
1174
- stepResult.status = "passed";
1175
- break;
1176
-
1177
- case "fill":
1178
- await this.page.fill(step.params.selector, step.params.value);
1179
- stepResult.status = "passed";
1180
- break;
1181
-
1182
- case "doubleClick":
1183
- const element = await this.page.$(step.params.selector);
1184
- if (element) {
1185
- await element.click();
1186
- await this.page.waitForTimeout(step.params.delay || 50);
1187
- await element.click();
1188
- }
1189
- stepResult.status = "passed";
1190
- break;
1191
-
1192
- case "goOffline":
1193
- await this.context.setOffline(true);
1194
- stepResult.status = "passed";
1195
- break;
1196
-
1197
- case "goOnline":
1198
- await this.context.setOffline(false);
1199
- stepResult.status = "passed";
1200
- break;
1201
-
1202
- case "clearCookies":
1203
- await this.context.clearCookies();
1204
- stepResult.status = "passed";
1205
- break;
1206
-
1207
- case "wait":
1208
- await this.page.waitForTimeout(step.params.ms);
1209
- stepResult.status = "passed";
1210
- break;
1211
-
1212
- case "goBack":
1213
- await this.page.goBack();
1214
- stepResult.status = "passed";
1215
- break;
1216
-
1217
- case "expect":
1218
- stepResult.actual = await this.evaluateExpectation(step.params);
1219
- stepResult.status = stepResult.actual.passed ? "passed" : "failed";
1220
- if (!stepResult.actual.passed) {
1221
- stepResult.error = stepResult.actual.reason;
1222
- }
1223
- break;
1224
-
1225
- case "recordMemory":
1226
- stepResult.memory = await this.page.evaluate(() => {
1227
- if (window.performance && window.performance.memory) {
1228
- return window.performance.memory.usedJSHeapSize;
1229
- }
1230
- return null;
1231
- });
1232
- stepResult.status = "passed";
1233
- break;
1234
-
1235
- case "interceptRequest":
1236
- await this.page.route(step.params.pattern, (route, request) => {
1237
- const postData = request.postData();
1238
- if (postData && step.params.modify) {
1239
- const modified = { ...JSON.parse(postData), ...step.params.modify };
1240
- route.continue({ postData: JSON.stringify(modified) });
1241
- } else {
1242
- route.continue();
1243
- }
1244
- });
1245
- stepResult.status = "passed";
1246
- break;
1247
-
1248
- case "interceptResponse":
1249
- await this.page.route(step.params.pattern, route => {
1250
- if (step.params.status) {
1251
- route.fulfill({
1252
- status: step.params.status,
1253
- body: step.params.body || ""
1254
- });
1255
- } else if (step.params.truncate) {
1256
- route.continue();
1257
- } else {
1258
- route.continue();
1259
- }
1260
- });
1261
- stepResult.status = "passed";
1262
- break;
1263
-
1264
- case "parallel":
1265
- // Execute parallel actions (simplified)
1266
- const promises = [];
1267
- for (let i = 0; i < step.params.count; i++) {
1268
- promises.push(this.executeStep({ action: step.params.action, params: step.params }));
1269
- }
1270
- await Promise.all(promises);
1271
- stepResult.status = "passed";
1272
- break;
1273
-
1274
- default:
1275
- stepResult.status = "skipped";
1276
- stepResult.reason = `Unknown action: ${step.action}`;
1277
- }
1278
- } catch (error) {
1279
- stepResult.status = "error";
1280
- stepResult.error = error.message;
1281
- }
1282
-
1283
- return stepResult;
1284
- }
1285
-
1286
- /**
1287
- * Evaluate an expectation
1288
- */
1289
- async evaluateExpectation(params) {
1290
- const result = { passed: true, checks: [] };
1291
-
1292
- // Check for redirect
1293
- if (params.redirect) {
1294
- const currentUrl = this.page.url();
1295
- const expectedUrl = this.resolveUrl(params.redirect);
1296
- const redirectPassed = currentUrl.includes(params.redirect);
1297
- result.checks.push({ type: "redirect", expected: expectedUrl, actual: currentUrl, passed: redirectPassed });
1298
- if (!redirectPassed && !params.or) result.passed = false;
1299
- }
1300
-
1301
- // Check for status code
1302
- if (params.status) {
1303
- // Would need to intercept the response to check this
1304
- result.checks.push({ type: "status", expected: params.status, passed: true });
1305
- }
1306
-
1307
- // Check for validation messages
1308
- if (params.validation) {
1309
- const hasValidation = await this.page.$("[class*='error'], [class*='invalid'], [aria-invalid='true']");
1310
- result.checks.push({ type: "validation", expected: true, actual: !!hasValidation, passed: !!hasValidation });
1311
- if (!hasValidation && !params.or) result.passed = false;
1312
- }
1313
-
1314
- // Check for user-friendly error (no stack trace)
1315
- if (params.noStackTrace) {
1316
- const pageContent = await this.page.content();
1317
- const hasStackTrace = /at\s+\w+\s+\(|Error:\s+/i.test(pageContent);
1318
- result.checks.push({ type: "noStackTrace", passed: !hasStackTrace });
1319
- if (hasStackTrace) {
1320
- result.passed = false;
1321
- result.reason = "Stack trace exposed to user";
1322
- }
1323
- }
1324
-
1325
- // Check for JS errors (page didn't crash)
1326
- if (params.noJsCrash) {
1327
- const hasContent = await this.page.$("body");
1328
- result.checks.push({ type: "noJsCrash", passed: !!hasContent });
1329
- }
1330
-
1331
- // Memory growth check
1332
- if (params.memoryGrowth) {
1333
- // Would compare recorded memory values
1334
- result.checks.push({ type: "memoryGrowth", passed: true });
1335
- }
1336
-
1337
- // Generate overall result
1338
- if (!result.passed) {
1339
- const failedChecks = result.checks.filter(c => !c.passed);
1340
- result.reason = failedChecks.map(c => `${c.type}: expected ${c.expected}, got ${c.actual}`).join("; ");
1341
- }
1342
-
1343
- return result;
1344
- }
1345
-
1346
- /**
1347
- * Resolve URL with base
1348
- */
1349
- resolveUrl(path) {
1350
- if (path.startsWith("http")) return path;
1351
- return new URL(path, this.baseUrl).toString();
1352
- }
1353
-
1354
- /**
1355
- * Get execution summary
1356
- */
1357
- getSummary() {
1358
- const passed = this.results.filter(r => r.status === "passed").length;
1359
- const failed = this.results.filter(r => r.status === "failed").length;
1360
- const errors = this.results.filter(r => r.status === "error").length;
1361
-
1362
- return {
1363
- total: this.results.length,
1364
- passed,
1365
- failed,
1366
- errors,
1367
- passRate: this.results.length > 0 ? (passed / this.results.length * 100).toFixed(1) : 0,
1368
- violations: this.results.flatMap(r => r.violations),
1369
- byCategory: this.groupByCategory(),
1370
- bySeverity: this.groupBySeverity()
1371
- };
1372
- }
1373
-
1374
- groupByCategory() {
1375
- const groups = {};
1376
- for (const result of this.results) {
1377
- if (!groups[result.category]) {
1378
- groups[result.category] = { passed: 0, failed: 0, errors: 0 };
1379
- }
1380
- groups[result.category][result.status]++;
1381
- }
1382
- return groups;
1383
- }
1384
-
1385
- groupBySeverity() {
1386
- const groups = { BLOCK: [], WARN: [] };
1387
- for (const result of this.results) {
1388
- if (result.status === "failed") {
1389
- groups[result.severity]?.push(result);
1390
- }
1391
- }
1392
- return groups;
1393
- }
1394
- }
1395
-
1396
- // ═══════════════════════════════════════════════════════════════════════════════
1397
- // EXPORTS
1398
- // ═══════════════════════════════════════════════════════════════════════════════
1399
-
1400
- module.exports = {
1401
- ScenarioGenerator,
1402
- ScenarioExecutor,
1403
- SCENARIO_TEMPLATES
1404
- };