tryassay 0.33.1 → 0.34.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 (126) hide show
  1. package/dist/cli.js +20 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/hunt.d.ts +2 -0
  4. package/dist/commands/hunt.js +58 -7
  5. package/dist/commands/hunt.js.map +1 -1
  6. package/dist/commands/mcp.d.ts +14 -0
  7. package/dist/commands/mcp.js +18 -0
  8. package/dist/commands/mcp.js.map +1 -0
  9. package/dist/commands/watch.d.ts +19 -0
  10. package/dist/commands/watch.js +158 -0
  11. package/dist/commands/watch.js.map +1 -0
  12. package/dist/hunt/__tests__/finding-to-template.test.d.ts +1 -0
  13. package/dist/hunt/__tests__/finding-to-template.test.js +213 -0
  14. package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -0
  15. package/dist/hunt/__tests__/parse-utils.test.js +28 -1
  16. package/dist/hunt/__tests__/parse-utils.test.js.map +1 -1
  17. package/dist/hunt/__tests__/taint-analysis.test.d.ts +1 -0
  18. package/dist/hunt/__tests__/taint-analysis.test.js +556 -0
  19. package/dist/hunt/__tests__/taint-analysis.test.js.map +1 -0
  20. package/dist/hunt/__tests__/templates.test.js +2 -2
  21. package/dist/hunt/__tests__/templates.test.js.map +1 -1
  22. package/dist/hunt/deep-dive.d.ts +2 -2
  23. package/dist/hunt/deep-dive.js +4 -4
  24. package/dist/hunt/deep-dive.js.map +1 -1
  25. package/dist/hunt/discovery.js +2 -2
  26. package/dist/hunt/discovery.js.map +1 -1
  27. package/dist/hunt/finding-to-template.d.ts +47 -0
  28. package/dist/hunt/finding-to-template.js +288 -0
  29. package/dist/hunt/finding-to-template.js.map +1 -0
  30. package/dist/hunt/orchestrator.d.ts +3 -0
  31. package/dist/hunt/orchestrator.js +20 -5
  32. package/dist/hunt/orchestrator.js.map +1 -1
  33. package/dist/hunt/taint-analysis.d.ts +49 -0
  34. package/dist/hunt/taint-analysis.js +429 -0
  35. package/dist/hunt/taint-analysis.js.map +1 -0
  36. package/dist/hunt/templates/csv-injection.d.ts +2 -0
  37. package/dist/hunt/templates/csv-injection.js +148 -0
  38. package/dist/hunt/templates/csv-injection.js.map +1 -0
  39. package/dist/hunt/templates/django-misconfig.d.ts +2 -0
  40. package/dist/hunt/templates/django-misconfig.js +172 -0
  41. package/dist/hunt/templates/django-misconfig.js.map +1 -0
  42. package/dist/hunt/templates/express-misconfig.d.ts +2 -0
  43. package/dist/hunt/templates/express-misconfig.js +156 -0
  44. package/dist/hunt/templates/express-misconfig.js.map +1 -0
  45. package/dist/hunt/templates/file-upload.d.ts +2 -0
  46. package/dist/hunt/templates/file-upload.js +131 -0
  47. package/dist/hunt/templates/file-upload.js.map +1 -0
  48. package/dist/hunt/templates/graphql-abuse.d.ts +2 -0
  49. package/dist/hunt/templates/graphql-abuse.js +161 -0
  50. package/dist/hunt/templates/graphql-abuse.js.map +1 -0
  51. package/dist/hunt/templates/hardcoded-credentials.d.ts +2 -0
  52. package/dist/hunt/templates/hardcoded-credentials.js +109 -0
  53. package/dist/hunt/templates/hardcoded-credentials.js.map +1 -0
  54. package/dist/hunt/templates/idor.d.ts +2 -0
  55. package/dist/hunt/templates/idor.js +102 -0
  56. package/dist/hunt/templates/idor.js.map +1 -0
  57. package/dist/hunt/templates/index.d.ts +2 -2
  58. package/dist/hunt/templates/index.js +38 -5
  59. package/dist/hunt/templates/index.js.map +1 -1
  60. package/dist/hunt/templates/insecure-deserialization.d.ts +2 -0
  61. package/dist/hunt/templates/insecure-deserialization.js +131 -0
  62. package/dist/hunt/templates/insecure-deserialization.js.map +1 -0
  63. package/dist/hunt/templates/mass-assignment.d.ts +2 -0
  64. package/dist/hunt/templates/mass-assignment.js +101 -0
  65. package/dist/hunt/templates/mass-assignment.js.map +1 -0
  66. package/dist/hunt/templates/nextjs-misconfig.d.ts +2 -0
  67. package/dist/hunt/templates/nextjs-misconfig.js +127 -0
  68. package/dist/hunt/templates/nextjs-misconfig.js.map +1 -0
  69. package/dist/hunt/templates/postmessage.d.ts +2 -0
  70. package/dist/hunt/templates/postmessage.js +180 -0
  71. package/dist/hunt/templates/postmessage.js.map +1 -0
  72. package/dist/hunt/templates/race-condition.d.ts +2 -0
  73. package/dist/hunt/templates/race-condition.js +138 -0
  74. package/dist/hunt/templates/race-condition.js.map +1 -0
  75. package/dist/hunt/templates/spring-misconfig.d.ts +2 -0
  76. package/dist/hunt/templates/spring-misconfig.js +177 -0
  77. package/dist/hunt/templates/spring-misconfig.js.map +1 -0
  78. package/dist/hunt/templates/xxe.d.ts +2 -0
  79. package/dist/hunt/templates/xxe.js +187 -0
  80. package/dist/hunt/templates/xxe.js.map +1 -0
  81. package/dist/hunt/triage.d.ts +2 -2
  82. package/dist/hunt/triage.js +4 -4
  83. package/dist/hunt/triage.js.map +1 -1
  84. package/dist/realtime/__tests__/catch-real-bugs.test.d.ts +9 -0
  85. package/dist/realtime/__tests__/catch-real-bugs.test.js +205 -0
  86. package/dist/realtime/__tests__/catch-real-bugs.test.js.map +1 -0
  87. package/dist/realtime/__tests__/code-buffer.test.d.ts +1 -0
  88. package/dist/realtime/__tests__/code-buffer.test.js +202 -0
  89. package/dist/realtime/__tests__/code-buffer.test.js.map +1 -0
  90. package/dist/realtime/__tests__/correction-injector.test.d.ts +1 -0
  91. package/dist/realtime/__tests__/correction-injector.test.js +168 -0
  92. package/dist/realtime/__tests__/correction-injector.test.js.map +1 -0
  93. package/dist/realtime/__tests__/stream-interceptor.test.d.ts +1 -0
  94. package/dist/realtime/__tests__/stream-interceptor.test.js +193 -0
  95. package/dist/realtime/__tests__/stream-interceptor.test.js.map +1 -0
  96. package/dist/realtime/__tests__/streaming-checks.test.d.ts +1 -0
  97. package/dist/realtime/__tests__/streaming-checks.test.js +479 -0
  98. package/dist/realtime/__tests__/streaming-checks.test.js.map +1 -0
  99. package/dist/realtime/__tests__/streaming-verifier.test.d.ts +1 -0
  100. package/dist/realtime/__tests__/streaming-verifier.test.js +157 -0
  101. package/dist/realtime/__tests__/streaming-verifier.test.js.map +1 -0
  102. package/dist/realtime/code-buffer.d.ts +52 -0
  103. package/dist/realtime/code-buffer.js +276 -0
  104. package/dist/realtime/code-buffer.js.map +1 -0
  105. package/dist/realtime/correction-injector.d.ts +56 -0
  106. package/dist/realtime/correction-injector.js +96 -0
  107. package/dist/realtime/correction-injector.js.map +1 -0
  108. package/dist/realtime/index.d.ts +14 -0
  109. package/dist/realtime/index.js +11 -0
  110. package/dist/realtime/index.js.map +1 -0
  111. package/dist/realtime/mcp-server.d.ts +14 -0
  112. package/dist/realtime/mcp-server.js +200 -0
  113. package/dist/realtime/mcp-server.js.map +1 -0
  114. package/dist/realtime/stream-interceptor.d.ts +65 -0
  115. package/dist/realtime/stream-interceptor.js +174 -0
  116. package/dist/realtime/stream-interceptor.js.map +1 -0
  117. package/dist/realtime/streaming-checks.d.ts +55 -0
  118. package/dist/realtime/streaming-checks.js +452 -0
  119. package/dist/realtime/streaming-checks.js.map +1 -0
  120. package/dist/realtime/streaming-verifier.d.ts +57 -0
  121. package/dist/realtime/streaming-verifier.js +134 -0
  122. package/dist/realtime/streaming-verifier.js.map +1 -0
  123. package/dist/realtime/types.d.ts +99 -0
  124. package/dist/realtime/types.js +8 -0
  125. package/dist/realtime/types.js.map +1 -0
  126. package/package.json +2 -1
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Streaming Checks — stateless, pre-compiled pattern matchers for real-time verification.
3
+ *
4
+ * Extracts the regex-based formal verification checks from the existing
5
+ * formal-verifier.ts into standalone StreamingCheck objects. Also loads
6
+ * learned rules from the catalog and compiles them into the same format.
7
+ *
8
+ * All regex patterns are created once at module load time and reused across
9
+ * invocations. The `runChecks` function targets <10ms for the full check set
10
+ * against a single code unit.
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ // ── Formal Checks (hand-crafted) ────────────────────────────────
15
+ const JS_LANGS = ['typescript', 'javascript', 'ts', 'js', 'tsx', 'jsx'];
16
+ const PY_LANGS = ['python', 'py'];
17
+ const ALL_LANGS = []; // empty = applies to all
18
+ /**
19
+ * Returns all hand-crafted formal checks as StreamingCheck objects.
20
+ *
21
+ * These cover the key vulnerability patterns from formal-verifier.ts,
22
+ * adapted for streaming (line/statement-level) detection during generation.
23
+ */
24
+ export function getFormalChecks() {
25
+ return FORMAL_CHECKS;
26
+ }
27
+ const FORMAL_CHECKS = [
28
+ // 1. SQL injection — string concatenation in queries
29
+ {
30
+ id: 'sql_concat_injection',
31
+ name: 'SQL string concatenation',
32
+ languages: [...JS_LANGS, ...PY_LANGS],
33
+ pattern: /(?:SELECT|INSERT|UPDATE|DELETE)\s[^;]*["'`]\s*\+\s*\w+/i,
34
+ verdict: 'FAIL',
35
+ severity: 'critical',
36
+ evidenceTemplate: 'SQL query uses string concatenation: $0',
37
+ suggestion: 'Use parameterized queries ($1, ?, or :named placeholders) instead of string concatenation.',
38
+ source: 'formal',
39
+ },
40
+ {
41
+ id: 'sql_template_injection',
42
+ name: 'SQL template literal injection',
43
+ languages: JS_LANGS,
44
+ pattern: /`[^`]*(?:SELECT|INSERT|UPDATE|DELETE)[^`]*\$\{(?!\d)/i,
45
+ verdict: 'FAIL',
46
+ severity: 'critical',
47
+ evidenceTemplate: 'SQL query uses template literal interpolation: $0',
48
+ suggestion: 'Use parameterized queries instead of template literal interpolation in SQL strings.',
49
+ source: 'formal',
50
+ },
51
+ // 2. Missing error handling — unhandled promise chains
52
+ {
53
+ id: 'unhandled_promise',
54
+ name: 'Unhandled promise (no .catch)',
55
+ languages: JS_LANGS,
56
+ pattern: /(?:fetch|axios\.\w+)\s*\([^)]*\)\s*\.then\s*\([^)]*\)(?![\s\S]*\.catch)/,
57
+ verdict: 'FAIL',
58
+ severity: 'high',
59
+ evidenceTemplate: 'Promise chain without .catch() handler: $0',
60
+ suggestion: 'Add a .catch() handler or use try/catch with await.',
61
+ source: 'formal',
62
+ },
63
+ // 3. Hardcoded credentials/secrets
64
+ {
65
+ id: 'hardcoded_secret',
66
+ name: 'Hardcoded secret or API key',
67
+ languages: ALL_LANGS,
68
+ pattern: /(?:const|let|var|=)\s*(?:\w+)?\s*(?:api[_-]?key|secret|password|token|auth)\s*=\s*["'`](?:sk-|pk-|ghp_|gho_|github_pat_|xox[bpors]-|AIza|AKIA|bearer\s).{8,}/i,
69
+ verdict: 'FAIL',
70
+ severity: 'critical',
71
+ evidenceTemplate: 'Hardcoded secret detected: $0',
72
+ suggestion: 'Use environment variables or a secrets manager instead of hardcoding credentials.',
73
+ source: 'formal',
74
+ },
75
+ // 4. Unsafe eval / Function constructor
76
+ {
77
+ id: 'unsafe_eval',
78
+ name: 'Unsafe eval() usage',
79
+ languages: JS_LANGS,
80
+ pattern: /\beval\s*\(\s*(?!\s*['"`][\s\S]*['"`]\s*\))/, // eval( but not eval("literal")
81
+ verdict: 'FAIL',
82
+ severity: 'critical',
83
+ evidenceTemplate: 'Unsafe eval() call detected: $0',
84
+ suggestion: 'Replace eval() with a safer alternative like JSON.parse() or a sandboxed evaluator.',
85
+ source: 'formal',
86
+ },
87
+ {
88
+ id: 'unsafe_function_constructor',
89
+ name: 'Unsafe Function constructor',
90
+ languages: JS_LANGS,
91
+ pattern: /new\s+Function\s*\(/,
92
+ verdict: 'FAIL',
93
+ severity: 'critical',
94
+ evidenceTemplate: 'Unsafe new Function() call detected: $0',
95
+ suggestion: 'Avoid new Function(). Use static code paths or a sandboxed evaluator.',
96
+ source: 'formal',
97
+ },
98
+ // 5. Insecure randomness (Math.random for security)
99
+ {
100
+ id: 'insecure_random',
101
+ name: 'Math.random() used for security',
102
+ languages: JS_LANGS,
103
+ pattern: /(?:token|secret|key|nonce|csrf|session|password|salt|hash)\s*(?:=|:)\s*.*Math\.random/i,
104
+ verdict: 'FAIL',
105
+ severity: 'high',
106
+ evidenceTemplate: 'Math.random() used in security-sensitive context: $0',
107
+ suggestion: 'Use crypto.randomUUID() or crypto.getRandomValues() for security-sensitive random values.',
108
+ source: 'formal',
109
+ },
110
+ {
111
+ id: 'insecure_random_token',
112
+ name: 'Math.random() token generation',
113
+ languages: JS_LANGS,
114
+ pattern: /Math\.random\(\)\.toString\(36\)/,
115
+ verdict: 'FAIL',
116
+ severity: 'high',
117
+ evidenceTemplate: 'Insecure token generation using Math.random(): $0',
118
+ suggestion: 'Use crypto.randomUUID() or crypto.randomBytes() for token generation.',
119
+ source: 'formal',
120
+ },
121
+ // 6. Prototype pollution patterns
122
+ {
123
+ id: 'prototype_pollution',
124
+ name: 'Prototype pollution risk',
125
+ languages: JS_LANGS,
126
+ pattern: /\w+\[(?:\w+|\w+\[\w+\])\]\s*=\s*(?!\s*(?:null|undefined|false|true|0|''|""|``)\s*[;,)])/,
127
+ verdict: 'FAIL',
128
+ severity: 'high',
129
+ evidenceTemplate: 'Dynamic property assignment may allow prototype pollution: $0',
130
+ suggestion: 'Validate property names against __proto__, constructor, and prototype before dynamic assignment.',
131
+ source: 'formal',
132
+ },
133
+ // 7. Path traversal
134
+ {
135
+ id: 'path_traversal',
136
+ name: 'Path traversal vulnerability',
137
+ languages: [...JS_LANGS, ...PY_LANGS],
138
+ pattern: /(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|open)\s*\(\s*(?:req\.(?:params|query|body)|request\.(?:args|form|json))\s*[\[.]/,
139
+ verdict: 'FAIL',
140
+ severity: 'critical',
141
+ evidenceTemplate: 'User input passed directly to filesystem operation: $0',
142
+ suggestion: 'Sanitize file paths with path.resolve() and verify they stay within the intended directory.',
143
+ source: 'formal',
144
+ },
145
+ // 8. Command injection
146
+ {
147
+ id: 'command_injection',
148
+ name: 'Command injection vulnerability',
149
+ languages: [...JS_LANGS, ...PY_LANGS],
150
+ pattern: /(?:exec|execSync|spawn|spawnSync|system|popen)\s*\(\s*(?:["'`][^"'`]*["'`]\s*\+|`[^`]*\$\{)/,
151
+ verdict: 'FAIL',
152
+ severity: 'critical',
153
+ evidenceTemplate: 'Command injection risk — shell command built with concatenation: $0',
154
+ suggestion: 'Use execFile() or spawn() with an argument array instead of exec() with string concatenation.',
155
+ source: 'formal',
156
+ },
157
+ // 9. Null/undefined access without check
158
+ {
159
+ id: 'unchecked_array_index',
160
+ name: 'Unchecked array index access',
161
+ languages: JS_LANGS,
162
+ pattern: /\w+\[0\]\.\w+(?!\s*\?\.)/, // arr[0].prop without optional chaining
163
+ verdict: 'FAIL',
164
+ severity: 'medium',
165
+ evidenceTemplate: 'Array element accessed without null check: $0',
166
+ suggestion: 'Check array length before accessing elements, or use optional chaining (arr[0]?.prop).',
167
+ source: 'formal',
168
+ },
169
+ // 10. Input validation missing — direct use of request params in DB queries
170
+ {
171
+ id: 'unvalidated_input_to_query',
172
+ name: 'Unvalidated input passed to database query',
173
+ languages: JS_LANGS,
174
+ pattern: /\.(?:query|execute|findOne|find|delete|update)\s*\(\s*\{[^}]*(?:req\.(?:params|query|body)|request\.\w+)/,
175
+ verdict: 'FAIL',
176
+ severity: 'high',
177
+ evidenceTemplate: 'Request input passed directly to database query without validation: $0',
178
+ suggestion: 'Validate and sanitize request input with zod, joi, or manual type checks before passing to queries.',
179
+ source: 'formal',
180
+ },
181
+ ];
182
+ // ── Learned Rule Conversion ─────────────────────────────────────
183
+ /**
184
+ * Keyword map: maps keyword triggers found in rule name/description/category
185
+ * to context keywords that must appear in code for the rule to be relevant.
186
+ *
187
+ * When a rule's name, description, or category contains a trigger (left side),
188
+ * the associated keywords (right side) are assigned. The check only runs if
189
+ * at least one keyword is found in the code unit text.
190
+ */
191
+ const CONTEXT_KEYWORD_MAP = [
192
+ // React / JSX
193
+ {
194
+ triggers: /\b(?:react|jsx|tsx|component|hook|use[A-Z]\w+|memo(?:iz)|useEffect|useState|useRef|useCallback|useMemo)\b/i,
195
+ keywords: ['React', 'useState', 'useEffect', 'useRef', 'useCallback', 'useMemo', 'jsx', 'tsx', 'import react', 'from \'react', 'from "react'],
196
+ },
197
+ // OpenAPI / Swagger / NestJS decorators
198
+ {
199
+ triggers: /\b(?:openapi|swagger|@Api\w*|@Controller|@Get|@Post|@Put|@Delete|@Patch|decorator|NestJS)\b/i,
200
+ keywords: ['@Api', '@Controller', '@Get(', '@Post(', '@Put(', '@Delete(', '@Patch(', 'swagger', 'openapi', '@nestjs'],
201
+ },
202
+ // Canvas / Drawing / 2D rendering
203
+ {
204
+ triggers: /\b(?:canvas|drawImage|getContext|2d\s*context|fillRect|strokeRect|beginPath|WebGL)\b/i,
205
+ keywords: ['canvas', 'drawImage', 'getContext', 'fillRect', 'strokeRect', 'beginPath', 'CanvasRenderingContext', 'webgl'],
206
+ },
207
+ // Stripe
208
+ {
209
+ triggers: /\b(?:stripe|payment.?intent|checkout.?session|subscription)\b/i,
210
+ keywords: ['stripe', 'Stripe', 'payment_intent', 'checkout.sessions', 'PaymentIntent'],
211
+ },
212
+ // GraphQL
213
+ {
214
+ triggers: /\b(?:graphql|resolver|mutation|gql`|@Query|@Mutation|@Resolver)\b/i,
215
+ keywords: ['graphql', 'GraphQL', 'gql`', '@Query', '@Mutation', '@Resolver', 'typeDefs', 'resolvers'],
216
+ },
217
+ // Docker / Container
218
+ {
219
+ triggers: /\b(?:docker|dockerfile|container|ENTRYPOINT|CMD\s)\b/i,
220
+ keywords: ['FROM ', 'ENTRYPOINT', 'CMD [', 'EXPOSE ', 'Dockerfile', 'docker-compose'],
221
+ },
222
+ // Redis
223
+ {
224
+ triggers: /\b(?:redis|HSET|HGET|ZADD|pub.?sub|ioredis)\b/i,
225
+ keywords: ['redis', 'Redis', 'ioredis', 'HSET', 'HGET', 'createClient'],
226
+ },
227
+ // MongoDB / Mongoose
228
+ {
229
+ triggers: /\b(?:mongo(?:db|ose)?|Schema\.Types|ObjectId|aggregate)\b/i,
230
+ keywords: ['mongoose', 'MongoDB', 'MongoClient', 'Schema.Types', 'ObjectId', '.aggregate('],
231
+ },
232
+ // Django
233
+ {
234
+ triggers: /\b(?:django|models\.Model|views\.py|urls\.py|INSTALLED_APPS)\b/i,
235
+ keywords: ['django', 'Django', 'models.Model', 'INSTALLED_APPS', 'urlpatterns', 'from django'],
236
+ },
237
+ // Flask
238
+ {
239
+ triggers: /\b(?:flask|@app\.route|Blueprint|Jinja)\b/i,
240
+ keywords: ['flask', 'Flask', '@app.route', 'Blueprint', 'from flask'],
241
+ },
242
+ // Vue
243
+ {
244
+ triggers: /\b(?:vue|v-model|v-bind|v-if|defineComponent|ref\(\)|computed\(\))\b/i,
245
+ keywords: ['vue', 'Vue', 'v-model', 'v-bind', 'defineComponent', 'from \'vue', 'from "vue'],
246
+ },
247
+ // Angular
248
+ {
249
+ triggers: /\b(?:angular|@Component|@Injectable|@NgModule|ngOnInit)\b/i,
250
+ keywords: ['@Component', '@Injectable', '@NgModule', 'ngOnInit', '@angular', 'from \'@angular'],
251
+ },
252
+ // WebSocket
253
+ {
254
+ triggers: /\b(?:websocket|ws:\/\/|wss:\/\/|socket\.io|onmessage)\b/i,
255
+ keywords: ['WebSocket', 'ws://', 'wss://', 'socket.io', 'onmessage', 'Socket('],
256
+ },
257
+ // AWS SDK
258
+ {
259
+ triggers: /\b(?:aws.?sdk|S3Client|DynamoDB|Lambda|SQS|SNS|@aws-sdk)\b/i,
260
+ keywords: ['aws-sdk', '@aws-sdk', 'S3Client', 'DynamoDB', 'LambdaClient', 'SQSClient'],
261
+ },
262
+ // Prisma
263
+ {
264
+ triggers: /\b(?:prisma|PrismaClient|prisma\.\$|findUnique|findMany)\b/i,
265
+ keywords: ['prisma', 'Prisma', 'PrismaClient', 'prisma.', '@prisma/client'],
266
+ },
267
+ ];
268
+ /**
269
+ * Derive context keywords for a learned rule based on its name, description,
270
+ * and category. Returns undefined if no specific context can be inferred
271
+ * (the rule will run against all code units).
272
+ */
273
+ function deriveContextKeywords(rule) {
274
+ const searchText = [
275
+ rule.pattern.description,
276
+ rule.pattern.claimCategory,
277
+ rule.id,
278
+ rule.fixDescription ?? '',
279
+ ].join(' ');
280
+ for (const entry of CONTEXT_KEYWORD_MAP) {
281
+ if (entry.triggers.test(searchText)) {
282
+ return entry.keywords;
283
+ }
284
+ }
285
+ // Fallback: try to extract literal strings from the regex pattern itself.
286
+ // Sequences of 4+ alphanumeric chars (likely meaningful identifiers).
287
+ if (rule.pattern.regexPattern) {
288
+ const literals = extractRegexLiterals(rule.pattern.regexPattern);
289
+ if (literals.length > 0) {
290
+ return literals;
291
+ }
292
+ }
293
+ return undefined;
294
+ }
295
+ /**
296
+ * Extract literal substrings (4+ alphanumeric chars) from a regex pattern.
297
+ * These are likely the actual identifiers the rule is looking for.
298
+ */
299
+ function extractRegexLiterals(regexStr) {
300
+ // Match sequences of word chars that are at least 4 chars long,
301
+ // excluding common regex syntax fragments
302
+ const REGEX_NOISE = new Set([
303
+ 'true', 'false', 'null', 'undefined', 'this', 'that',
304
+ 'with', 'from', 'each', 'some', 'every', 'filter',
305
+ ]);
306
+ const matches = regexStr.match(/[a-zA-Z_][a-zA-Z0-9_]{3,}/g) ?? [];
307
+ const unique = [...new Set(matches)].filter(m => !REGEX_NOISE.has(m.toLowerCase()));
308
+ return unique.length <= 10 ? unique : unique.slice(0, 10);
309
+ }
310
+ /**
311
+ * Load learned rules from the catalog and convert them to StreamingCheck format.
312
+ *
313
+ * Default path: `.assay/learned/rules.json` relative to process.cwd().
314
+ * Falls back to an empty set if the file doesn't exist (starter rules
315
+ * are loaded separately by getFormalChecks equivalent in the catalog).
316
+ *
317
+ * Each rule is assigned contextKeywords for relevance filtering — the check
318
+ * only runs on code units that contain at least one keyword.
319
+ */
320
+ export function loadLearnedChecks(catalogPath) {
321
+ const filePath = catalogPath ?? join(process.cwd(), '.assay', 'learned', 'rules.json');
322
+ let rules;
323
+ try {
324
+ const content = readFileSync(filePath, 'utf-8');
325
+ const parsed = JSON.parse(content);
326
+ rules = Array.isArray(parsed) ? parsed : [];
327
+ }
328
+ catch {
329
+ // No catalog on disk — return empty (starter rules are part of formal checks)
330
+ return [];
331
+ }
332
+ return rules
333
+ .filter(r => r.status === 'promoted' || r.status === 'validated')
334
+ .filter(r => r.pattern.kind === 'regex' && r.pattern.regexPattern)
335
+ .map(r => learnedRuleToCheck(r));
336
+ }
337
+ function learnedRuleToCheck(rule) {
338
+ let pattern;
339
+ try {
340
+ pattern = new RegExp(rule.pattern.regexPattern, 'i');
341
+ }
342
+ catch {
343
+ // Invalid regex — create a pattern that never matches
344
+ pattern = /(?!)/;
345
+ }
346
+ return {
347
+ id: `learned_${rule.id}`,
348
+ name: rule.pattern.description.slice(0, 80),
349
+ languages: [...rule.pattern.languages],
350
+ pattern,
351
+ verdict: rule.pattern.matchBehavior === 'presence_is_bad' ? 'FAIL' : 'PASS',
352
+ severity: rule.pattern.severity,
353
+ evidenceTemplate: rule.pattern.evidenceTemplate,
354
+ suggestion: rule.fixDescription,
355
+ source: 'learned',
356
+ contextKeywords: deriveContextKeywords(rule),
357
+ };
358
+ }
359
+ // Exported for testing
360
+ export { deriveContextKeywords as _deriveContextKeywords, extractRegexLiterals as _extractRegexLiterals };
361
+ // ── Compilation ─────────────────────────────────────────────────
362
+ /**
363
+ * Filter checks by language and return a pre-compiled set ready for fast execution.
364
+ *
365
+ * If no checks are provided, uses the built-in formal checks.
366
+ */
367
+ export function compileCheckSet(language, checks) {
368
+ const start = performance.now();
369
+ const allChecks = checks ?? FORMAL_CHECKS;
370
+ const langLower = language.toLowerCase();
371
+ const filtered = allChecks.filter(check => {
372
+ if (check.languages.length === 0)
373
+ return true; // applies to all languages
374
+ return check.languages.some(l => l.toLowerCase() === langLower);
375
+ });
376
+ const compileTimeMs = performance.now() - start;
377
+ return {
378
+ checks: filtered,
379
+ language: langLower,
380
+ compileTimeMs,
381
+ };
382
+ }
383
+ // ── Execution ───────────────────────────────────────────────────
384
+ /**
385
+ * Run all checks in the set against a code unit. Returns verification events.
386
+ *
387
+ * Designed to complete in <10ms for the full check set against a typical code unit.
388
+ */
389
+ export function runChecks(unit, checkSet) {
390
+ const events = [];
391
+ const unitStart = performance.now();
392
+ const textLower = unit.text.toLowerCase();
393
+ for (const check of checkSet.checks) {
394
+ // Level 2 filtering: skip checks whose contextKeywords don't match the code
395
+ if (check.contextKeywords && check.contextKeywords.length > 0) {
396
+ const hasRelevantKeyword = check.contextKeywords.some(kw => textLower.includes(kw.toLowerCase()));
397
+ if (!hasRelevantKeyword)
398
+ continue;
399
+ }
400
+ const checkStart = performance.now();
401
+ // Reset lastIndex for patterns with the global flag
402
+ check.pattern.lastIndex = 0;
403
+ const match = check.pattern.exec(unit.text);
404
+ const latencyMs = performance.now() - checkStart;
405
+ if (match && check.verdict === 'FAIL') {
406
+ // Pattern matched and that means a failure
407
+ const evidence = formatEvidence(check.evidenceTemplate, match);
408
+ events.push({
409
+ type: 'finding',
410
+ codeUnit: unit,
411
+ checkId: check.id,
412
+ checkName: check.name,
413
+ verdict: 'FAIL',
414
+ evidence,
415
+ severity: check.severity,
416
+ suggestion: check.suggestion,
417
+ latencyMs,
418
+ });
419
+ }
420
+ else if (!match && check.verdict === 'PASS') {
421
+ // Pattern was supposed to match (PASS means "match = good") but didn't
422
+ // This is the absence_is_bad case from learned rules
423
+ events.push({
424
+ type: 'finding',
425
+ codeUnit: unit,
426
+ checkId: check.id,
427
+ checkName: check.name,
428
+ verdict: 'FAIL',
429
+ evidence: formatEvidence(check.evidenceTemplate, null),
430
+ severity: check.severity,
431
+ suggestion: check.suggestion,
432
+ latencyMs,
433
+ });
434
+ }
435
+ // Other cases: match+PASS (good) or no-match+FAIL (no problem found) — no event
436
+ }
437
+ return events;
438
+ }
439
+ // ── Helpers ─────────────────────────────────────────────────────
440
+ function formatEvidence(template, match) {
441
+ if (!match)
442
+ return template;
443
+ let result = template;
444
+ // Replace $0 with full match
445
+ result = result.replace(/\$0/g, match[0]);
446
+ // Replace $1, $2, etc. with capture groups
447
+ for (let i = 1; i < match.length; i++) {
448
+ result = result.replace(new RegExp(`\\$${i}`, 'g'), match[i] ?? '');
449
+ }
450
+ return result;
451
+ }
452
+ //# sourceMappingURL=streaming-checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-checks.js","sourceRoot":"","sources":["../../src/realtime/streaming-checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * StreamingVerifier — orchestrator for the real-time verification pipeline.
3
+ *
4
+ * Wires CodeBuffer (token accumulation + code unit detection) with
5
+ * StreamingChecks (pattern-based formal + learned checks) into a single
6
+ * stateful pipeline. Feed it tokens, it buffers them, detects completed
7
+ * code units, runs checks, and emits verification events.
8
+ */
9
+ import type { CheckSeverity, StreamingVerifierStats, VerificationEvent } from './types.js';
10
+ export interface StreamingVerifierOptions {
11
+ language: string;
12
+ /** Path to project root for loading learned rules catalog */
13
+ projectPath?: string;
14
+ /** Callback fired for each verification event */
15
+ onEvent?: (event: VerificationEvent) => void;
16
+ /** Only auto-correct findings at or above this severity. Default: 'high' */
17
+ minCorrectionSeverity?: CheckSeverity;
18
+ }
19
+ export declare class StreamingVerifier {
20
+ private readonly codeBuffer;
21
+ private readonly checkSet;
22
+ private readonly onEvent?;
23
+ private events;
24
+ private unitsProcessed;
25
+ private checksRun;
26
+ private findingsCount;
27
+ private totalTimeMs;
28
+ private maxLatencyMs;
29
+ /**
30
+ * Tracks the highest endOffset from any code unit we have processed.
31
+ * Used by flush() to know where unchecked trailing code begins.
32
+ */
33
+ private coveredOffset;
34
+ constructor(options: StreamingVerifierOptions);
35
+ /**
36
+ * Feed tokens from the LLM stream. Returns any verification events triggered.
37
+ */
38
+ push(tokens: string): VerificationEvent[];
39
+ /**
40
+ * Flush the buffer — run checks on any remaining buffered code.
41
+ * Call at the end of the stream to verify trailing code that never
42
+ * reached a structural boundary (closing brace, semicolon, etc.).
43
+ */
44
+ flush(): VerificationEvent[];
45
+ /** Get cumulative statistics. */
46
+ getStats(): StreamingVerifierStats;
47
+ /** Get all findings (FAIL events) accumulated so far. */
48
+ getFindings(): VerificationEvent[];
49
+ /** Get the full generated code accumulated so far. */
50
+ getGeneratedCode(): string;
51
+ /** Reset all state — buffer, events, stats. */
52
+ reset(): void;
53
+ /**
54
+ * Run checks against a list of code units, accumulate stats, fire callbacks.
55
+ */
56
+ private processUnits;
57
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * StreamingVerifier — orchestrator for the real-time verification pipeline.
3
+ *
4
+ * Wires CodeBuffer (token accumulation + code unit detection) with
5
+ * StreamingChecks (pattern-based formal + learned checks) into a single
6
+ * stateful pipeline. Feed it tokens, it buffers them, detects completed
7
+ * code units, runs checks, and emits verification events.
8
+ */
9
+ import { CodeBuffer } from './code-buffer.js';
10
+ import { getFormalChecks, loadLearnedChecks, compileCheckSet, runChecks, } from './streaming-checks.js';
11
+ // ---------------------------------------------------------------------------
12
+ // StreamingVerifier
13
+ // ---------------------------------------------------------------------------
14
+ export class StreamingVerifier {
15
+ codeBuffer;
16
+ checkSet;
17
+ onEvent;
18
+ // Accumulated state
19
+ events = [];
20
+ unitsProcessed = 0;
21
+ checksRun = 0;
22
+ findingsCount = 0;
23
+ totalTimeMs = 0;
24
+ maxLatencyMs = 0;
25
+ /**
26
+ * Tracks the highest endOffset from any code unit we have processed.
27
+ * Used by flush() to know where unchecked trailing code begins.
28
+ */
29
+ coveredOffset = 0;
30
+ constructor(options) {
31
+ this.codeBuffer = new CodeBuffer(options.language);
32
+ this.onEvent = options.onEvent;
33
+ // Load formal checks
34
+ const formal = getFormalChecks();
35
+ // Optionally load learned rules from the project catalog
36
+ let learned = [];
37
+ if (options.projectPath) {
38
+ const catalogPath = `${options.projectPath}/.assay/learned/rules.json`;
39
+ learned = loadLearnedChecks(catalogPath);
40
+ }
41
+ // Compile into a single check set filtered by language
42
+ this.checkSet = compileCheckSet(options.language, [...formal, ...learned]);
43
+ }
44
+ // -- Public API -----------------------------------------------------------
45
+ /**
46
+ * Feed tokens from the LLM stream. Returns any verification events triggered.
47
+ */
48
+ push(tokens) {
49
+ const units = this.codeBuffer.push(tokens);
50
+ return this.processUnits(units);
51
+ }
52
+ /**
53
+ * Flush the buffer — run checks on any remaining buffered code.
54
+ * Call at the end of the stream to verify trailing code that never
55
+ * reached a structural boundary (closing brace, semicolon, etc.).
56
+ */
57
+ flush() {
58
+ const fullBuffer = this.codeBuffer.getFullBuffer();
59
+ const remaining = fullBuffer.slice(this.coveredOffset).trim();
60
+ if (remaining.length === 0) {
61
+ return [];
62
+ }
63
+ const trailingUnit = {
64
+ text: remaining,
65
+ kind: 'line',
66
+ startOffset: this.coveredOffset,
67
+ endOffset: fullBuffer.length,
68
+ language: this.codeBuffer.getLanguage(),
69
+ };
70
+ return this.processUnits([trailingUnit]);
71
+ }
72
+ /** Get cumulative statistics. */
73
+ getStats() {
74
+ return {
75
+ unitsProcessed: this.unitsProcessed,
76
+ checksRun: this.checksRun,
77
+ findings: this.findingsCount,
78
+ avgLatencyMs: this.unitsProcessed > 0 ? this.totalTimeMs / this.unitsProcessed : 0,
79
+ maxLatencyMs: this.maxLatencyMs,
80
+ totalTimeMs: this.totalTimeMs,
81
+ };
82
+ }
83
+ /** Get all findings (FAIL events) accumulated so far. */
84
+ getFindings() {
85
+ return this.events.filter(e => e.verdict === 'FAIL');
86
+ }
87
+ /** Get the full generated code accumulated so far. */
88
+ getGeneratedCode() {
89
+ return this.codeBuffer.getFullBuffer();
90
+ }
91
+ /** Reset all state — buffer, events, stats. */
92
+ reset() {
93
+ this.codeBuffer.reset();
94
+ this.events = [];
95
+ this.unitsProcessed = 0;
96
+ this.checksRun = 0;
97
+ this.findingsCount = 0;
98
+ this.totalTimeMs = 0;
99
+ this.maxLatencyMs = 0;
100
+ this.coveredOffset = 0;
101
+ }
102
+ // -- Private helpers ------------------------------------------------------
103
+ /**
104
+ * Run checks against a list of code units, accumulate stats, fire callbacks.
105
+ */
106
+ processUnits(units) {
107
+ const batchEvents = [];
108
+ for (const unit of units) {
109
+ const unitStart = performance.now();
110
+ const unitEvents = runChecks(unit, this.checkSet);
111
+ const unitLatency = performance.now() - unitStart;
112
+ // Track coverage regardless of whether events were produced
113
+ if (unit.endOffset > this.coveredOffset) {
114
+ this.coveredOffset = unit.endOffset;
115
+ }
116
+ this.unitsProcessed++;
117
+ this.checksRun += this.checkSet.checks.length;
118
+ this.totalTimeMs += unitLatency;
119
+ if (unitLatency > this.maxLatencyMs) {
120
+ this.maxLatencyMs = unitLatency;
121
+ }
122
+ for (const event of unitEvents) {
123
+ this.events.push(event);
124
+ batchEvents.push(event);
125
+ if (event.verdict === 'FAIL') {
126
+ this.findingsCount++;
127
+ }
128
+ this.onEvent?.(event);
129
+ }
130
+ }
131
+ return batchEvents;
132
+ }
133
+ }
134
+ //# sourceMappingURL=streaming-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-verifier.js","sourceRoot":"","sources":["../../src/realtime/streaming-verifier.ts"],"names":[],"mappings":"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"}