tryassay 0.34.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/bayesian/__tests__/bas-calculator.test.d.ts +1 -0
  2. package/dist/bayesian/__tests__/bas-calculator.test.js +63 -0
  3. package/dist/bayesian/__tests__/bas-calculator.test.js.map +1 -0
  4. package/dist/bayesian/__tests__/structural-entropy.test.d.ts +1 -0
  5. package/dist/bayesian/__tests__/structural-entropy.test.js +21 -0
  6. package/dist/bayesian/__tests__/structural-entropy.test.js.map +1 -0
  7. package/dist/bayesian/bas-calculator.d.ts +41 -0
  8. package/dist/bayesian/bas-calculator.js +198 -0
  9. package/dist/bayesian/bas-calculator.js.map +1 -0
  10. package/dist/bayesian/index.d.ts +3 -0
  11. package/dist/bayesian/index.js +3 -0
  12. package/dist/bayesian/index.js.map +1 -0
  13. package/dist/bayesian/structural-entropy.d.ts +12 -0
  14. package/dist/bayesian/structural-entropy.js +37 -0
  15. package/dist/bayesian/structural-entropy.js.map +1 -0
  16. package/dist/bayesian/types.d.ts +37 -0
  17. package/dist/bayesian/types.js +6 -0
  18. package/dist/bayesian/types.js.map +1 -0
  19. package/dist/cli.js +28 -0
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/__tests__/assess-formal.test.d.ts +1 -0
  22. package/dist/commands/__tests__/assess-formal.test.js +72 -0
  23. package/dist/commands/__tests__/assess-formal.test.js.map +1 -0
  24. package/dist/commands/activate.d.ts +1 -0
  25. package/dist/commands/activate.js +48 -0
  26. package/dist/commands/activate.js.map +1 -0
  27. package/dist/commands/assess.js +100 -5
  28. package/dist/commands/assess.js.map +1 -1
  29. package/dist/commands/bas-score.d.ts +13 -0
  30. package/dist/commands/bas-score.js +310 -0
  31. package/dist/commands/bas-score.js.map +1 -0
  32. package/dist/commands/bounty-watch.js.map +1 -1
  33. package/dist/commands/hunt.js +32 -0
  34. package/dist/commands/hunt.js.map +1 -1
  35. package/dist/commands/runtime.js +11 -10
  36. package/dist/commands/runtime.js.map +1 -1
  37. package/dist/commands/stream-verify.d.ts +16 -0
  38. package/dist/commands/stream-verify.js +228 -0
  39. package/dist/commands/stream-verify.js.map +1 -0
  40. package/dist/commands/watch.js.map +1 -1
  41. package/dist/hunt/__tests__/deep-dive.test.js.map +1 -1
  42. package/dist/hunt/__tests__/e2e.test.js.map +1 -1
  43. package/dist/hunt/__tests__/finding-to-template.test.js +10 -1
  44. package/dist/hunt/__tests__/finding-to-template.test.js.map +1 -1
  45. package/dist/hunt/__tests__/orchestrator.test.js.map +1 -1
  46. package/dist/hunt/__tests__/templates.test.js +2 -2
  47. package/dist/hunt/__tests__/triage.test.js.map +1 -1
  48. package/dist/hunt/deep-dive.js +7 -7
  49. package/dist/hunt/deep-dive.js.map +1 -1
  50. package/dist/hunt/parse-utils.d.ts +1 -1
  51. package/dist/hunt/state.js.map +1 -1
  52. package/dist/hunt/templates/injection.js +1 -1
  53. package/dist/hunt/templates/injection.js.map +1 -1
  54. package/dist/hunt/triage.js +5 -5
  55. package/dist/hunt/triage.js.map +1 -1
  56. package/dist/lib/__tests__/arithmetic-quick-test.js +10 -9
  57. package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -1
  58. package/dist/lib/__tests__/arithmetic-real-llm-test.js +8 -8
  59. package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -1
  60. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +4 -3
  61. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -1
  62. package/dist/lib/__tests__/formal-verifier-behavioral.test.d.ts +18 -0
  63. package/dist/lib/__tests__/formal-verifier-behavioral.test.js +576 -0
  64. package/dist/lib/__tests__/formal-verifier-behavioral.test.js.map +1 -0
  65. package/dist/lib/__tests__/formal-verifier-claimless-async.test.d.ts +1 -0
  66. package/dist/lib/__tests__/formal-verifier-claimless-async.test.js +154 -0
  67. package/dist/lib/__tests__/formal-verifier-claimless-async.test.js.map +1 -0
  68. package/dist/lib/__tests__/formal-verifier-claimless-quality.test.d.ts +1 -0
  69. package/dist/lib/__tests__/formal-verifier-claimless-quality.test.js +121 -0
  70. package/dist/lib/__tests__/formal-verifier-claimless-quality.test.js.map +1 -0
  71. package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.d.ts +1 -0
  72. package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.js +119 -0
  73. package/dist/lib/__tests__/formal-verifier-claimless-realworld.test.js.map +1 -0
  74. package/dist/lib/__tests__/formal-verifier-claimless.test.d.ts +1 -0
  75. package/dist/lib/__tests__/formal-verifier-claimless.test.js +667 -0
  76. package/dist/lib/__tests__/formal-verifier-claimless.test.js.map +1 -0
  77. package/dist/lib/__tests__/mr-gsm8k-benchmark.js +6 -6
  78. package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -1
  79. package/dist/lib/__tests__/pr-harvester.test.js.map +1 -1
  80. package/dist/lib/assessment-reporter.d.ts +1 -1
  81. package/dist/lib/assessment-reporter.js +2 -1
  82. package/dist/lib/assessment-reporter.js.map +1 -1
  83. package/dist/lib/chain-analyzer.d.ts +4 -3
  84. package/dist/lib/chain-analyzer.js.map +1 -1
  85. package/dist/lib/formal-verifier.d.ts +20 -1
  86. package/dist/lib/formal-verifier.js +1182 -24
  87. package/dist/lib/formal-verifier.js.map +1 -1
  88. package/dist/lib/issue-reporter.d.ts +2 -1
  89. package/dist/lib/issue-reporter.js.map +1 -1
  90. package/dist/lib/remediation-generator.js.map +1 -1
  91. package/dist/lib/report-generator.js.map +1 -1
  92. package/dist/lib/rule-harvester/ground-truth.js +13 -2
  93. package/dist/lib/rule-harvester/ground-truth.js.map +1 -1
  94. package/dist/lib/rule-harvester/scanner.d.ts +1 -1
  95. package/dist/lib/user-config.d.ts +1 -0
  96. package/dist/lib/user-config.js.map +1 -1
  97. package/dist/realtime/__tests__/entropy-detector.test.d.ts +1 -0
  98. package/dist/realtime/__tests__/entropy-detector.test.js +200 -0
  99. package/dist/realtime/__tests__/entropy-detector.test.js.map +1 -0
  100. package/dist/realtime/__tests__/entropy-live-demo.d.ts +1 -0
  101. package/dist/realtime/__tests__/entropy-live-demo.js +103 -0
  102. package/dist/realtime/__tests__/entropy-live-demo.js.map +1 -0
  103. package/dist/realtime/__tests__/entropy-live.d.ts +8 -0
  104. package/dist/realtime/__tests__/entropy-live.js +114 -0
  105. package/dist/realtime/__tests__/entropy-live.js.map +1 -0
  106. package/dist/realtime/__tests__/streaming-checks.test.js +3 -4
  107. package/dist/realtime/__tests__/streaming-checks.test.js.map +1 -1
  108. package/dist/realtime/entropy-detector.d.ts +143 -0
  109. package/dist/realtime/entropy-detector.js +504 -0
  110. package/dist/realtime/entropy-detector.js.map +1 -0
  111. package/dist/realtime/mcp-server.d.ts +7 -1
  112. package/dist/realtime/mcp-server.js +378 -2
  113. package/dist/realtime/mcp-server.js.map +1 -1
  114. package/dist/realtime/stream-interceptor.d.ts +28 -0
  115. package/dist/realtime/stream-interceptor.js +204 -0
  116. package/dist/realtime/stream-interceptor.js.map +1 -1
  117. package/dist/realtime/streaming-checks.js +28 -0
  118. package/dist/realtime/streaming-checks.js.map +1 -1
  119. package/dist/realtime/streaming-verifier.d.ts +45 -0
  120. package/dist/realtime/streaming-verifier.js +98 -5
  121. package/dist/realtime/streaming-verifier.js.map +1 -1
  122. package/dist/realtime/types.d.ts +56 -0
  123. package/dist/runtime/agents/research-agent.js +10 -1
  124. package/dist/runtime/agents/research-agent.js.map +1 -1
  125. package/dist/runtime/agents/test-agent.js +10 -7
  126. package/dist/runtime/agents/test-agent.js.map +1 -1
  127. package/dist/runtime/composition-verifier.js +13 -3
  128. package/dist/runtime/composition-verifier.js.map +1 -1
  129. package/dist/runtime/fs-helpers.js.map +1 -1
  130. package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
  131. package/dist/sdk/verified-generate.js.map +1 -1
  132. package/dist/types.d.ts +14 -0
  133. package/package.json +3 -2
@@ -175,7 +175,8 @@ function preprocessCode(code) {
175
175
  const KNOWN_BAD_APIS = {
176
176
  // JavaScript/TypeScript — non-existent array methods
177
177
  '.flatten(': { languages: ['js', 'ts', 'jsx', 'tsx', 'javascript', 'typescript'], correct: '.flat(' },
178
- '.contains(': { languages: ['js', 'ts', 'jsx', 'tsx', 'javascript', 'typescript'], correct: '.includes(' },
178
+ // NOTE: .contains() is NOT a bad API it exists on Node, DOMTokenList, DOMStringList,
179
+ // and Supabase query builders. Removed after real-world audit showed 100% false positive rate.
179
180
  // Non-existent Object methods
180
181
  'Object.fromPairs(': { languages: ['js', 'ts', 'jsx', 'tsx', 'javascript', 'typescript'], correct: 'Object.fromEntries(' },
181
182
  'Object.pairs(': { languages: ['js', 'ts', 'jsx', 'tsx', 'javascript', 'typescript'], correct: 'Object.entries(' },
@@ -223,7 +224,7 @@ const PY_BUILTINS = new Set([
223
224
  'functools', 'itertools', 'typing', 'pathlib', 'unittest', 'pytest',
224
225
  ]);
225
226
  // ── Claim Classification ─────────────────────────────────────
226
- function classifyClaim(claim) {
227
+ export function classifyClaim(claim) {
227
228
  const text = `${claim.description} ${claim.assertion}`.toLowerCase();
228
229
  const originalText = `${claim.description} ${claim.assertion}`;
229
230
  const funcExistsMatch = originalText.match(/(?:function|method|class|constructor)\s+['"`]?(\w+)['"`]?\s+(?:exists?|is\s+defined|is\s+declared|should\s+exist)/i);
@@ -254,30 +255,43 @@ function classifyClaim(claim) {
254
255
  if (/(?:parameter|argument)\s+['"`]?(\w+)['"`]?\s+(?:is|of\s+type)/i.test(text)) {
255
256
  return { claim, formallyVerifiable: true, checkType: 'parameter_check' };
256
257
  }
257
- if (claim.category === 'error-handling' &&
258
- /(?:handles?\s+(?:errors?|exceptions?|failures?)|try[\s-]catch|error\s+handling|catches?\s+(?:errors?|exceptions?)|wraps?\s+in\s+try)/i.test(text)) {
258
+ if (/(?:handles?\s+(?:errors?|exceptions?|failures?)|try[\s-]catch|error\s+handling|catches?\s+(?:errors?|exceptions?)|wraps?\s+in\s+try)/i.test(text) ||
259
+ /(?:catch(?:es)?\s+rejected\s+promises?|network\s+failures?\s+(?:are\s+)?caught)/i.test(text) ||
260
+ /(?:graceful(?:ly)?\s+(?:handle|fail|degrade|recover))/i.test(text)) {
259
261
  return { claim, formallyVerifiable: true, checkType: 'error_handling' };
260
262
  }
261
263
  if (/(?:null|undefined|nil|none)\s+(?:check|guard|validation|handling|safety)/i.test(text) ||
262
264
  /(?:checks?\s+(?:for\s+)?(?:null|undefined|nil|none))|(?:handles?\s+(?:null|undefined|nil|none))/i.test(text) ||
263
- /(?:validates?\s+(?:that\s+)?(?:input|parameter|argument)\s+(?:is\s+not\s+)?(?:null|undefined|nil|none))/i.test(text)) {
265
+ /(?:validates?\s+(?:that\s+)?(?:input|parameter|argument)\s+(?:is\s+not\s+)?(?:null|undefined|nil|none))/i.test(text) ||
266
+ /(?:undefined|null)\s+(?:\w+\s+){0,3}(?:handled|checked|guarded|safe)/i.test(text) ||
267
+ /returns?\s+null\b/i.test(text)) {
264
268
  return { claim, formallyVerifiable: true, checkType: 'null_check' };
265
269
  }
266
- if (claim.category === 'type-safety' &&
267
- /(?:type\s+annot|typed\s+(?:as|return)|return\s+type|type-safe|strongly\s+typed)/i.test(text)) {
270
+ if (/(?:type\s+annot|typed\s+(?:as|return)|return\s+type|type-safe|strongly\s+typed)/i.test(text) ||
271
+ /(?:typescript\s+type|have\s+(?:typescript\s+)?type\s+annotations?)/i.test(text) ||
272
+ /(?:return\s+types?\s+(?:are\s+)?(?:explicitly\s+)?declared)/i.test(text)) {
268
273
  return { claim, formallyVerifiable: true, checkType: 'type_annotation' };
269
274
  }
270
- if (claim.category === 'security' &&
271
- /(?:sql\s+injection|parameterized\s+quer|prepared\s+statement|sql\s+sanitiz|query\s+parameteriz)/i.test(text)) {
275
+ if (/(?:sql\s+injection|parameterized\s+quer|prepared\s+statement|sql\s+sanitiz|query\s+parameteriz)/i.test(text)) {
272
276
  return { claim, formallyVerifiable: true, checkType: 'sql_parameterized' };
273
277
  }
274
- if (/(?:validates?\s+(?:user\s+)?input|input\s+validation|sanitizes?\s+input|validates?\s+(?:before|prior))/i.test(text)) {
278
+ if (/(?:validates?\s+(?:user\s+)?input|input\s+validation|sanitizes?\s+input|validates?\s+(?:before|prior))/i.test(text) ||
279
+ /(?:(?:email|phone|url|format|field|body|parameter)\s+(?:is\s+)?(?:validated?|sanitized?|checked))/i.test(text) ||
280
+ /(?:(?:validated?|sanitized?|checked)\s+(?:on\s+the\s+)?(?:server|backend|api))/i.test(text) ||
281
+ /(?:request\s+body\s+(?:fields?\s+)?(?:are\s+)?(?:validated?|sanitized?|checked))/i.test(text)) {
275
282
  return { claim, formallyVerifiable: true, checkType: 'input_validation' };
276
283
  }
284
+ // Undefined reference checks — must come before arithmetic to avoid "for calculations" matching arithmetic
285
+ if (/(?:references?|uses?|accesses?)\s+(?:variable|function|import|module|identifier)\s+['"`]?(\w+)['"`]?/i.test(text) ||
286
+ /(?:variable|function|import)\s+['"`]?(\w+)['"`]?\s+(?:is\s+)?(?:defined|declared|imported|available)/i.test(text)) {
287
+ return { claim, formallyVerifiable: true, checkType: 'undefined_reference' };
288
+ }
277
289
  // Arithmetic correctness checks
278
- if (/(?:calculates?|computes?|evaluates?\s+to|sums?|totals?|multiplies?|divides?|averages?|rounds?)/i.test(text) ||
290
+ if (/(?:calculates?|calculation|computes?|computation|evaluates?\s+to|sums?|totals?|multiplies?|divides?|averages?|rounds?)/i.test(text) ||
279
291
  /(?:returns?\s+(?:the\s+)?(?:sum|product|average|total|difference|ratio|percentage|remainder|quotient))/i.test(text) ||
280
- /(?:result\s+(?:is|equals?|should\s+be)\s+\d)/i.test(text)) {
292
+ /(?:result\s+(?:is|equals?|should\s+be)\s+\d)/i.test(text) ||
293
+ /(?:\d+\s*[+\-*/]\s*\d+\s*=\s*\d)/i.test(text) ||
294
+ /(?:percentage|percent|ratio)\s*:\s*\d/i.test(text)) {
281
295
  return { claim, formallyVerifiable: true, checkType: 'arithmetic_correctness' };
282
296
  }
283
297
  // API misuse checks
@@ -285,10 +299,100 @@ function classifyClaim(claim) {
285
299
  /(?:calls?\s+\w+\.\w+)|(?:uses?\s+\w+\.\w+\s*\()/i.test(originalText)) {
286
300
  return { claim, formallyVerifiable: true, checkType: 'api_misuse' };
287
301
  }
288
- // Undefined reference checks
289
- if (/(?:references?|uses?|accesses?)\s+(?:variable|function|import|module|identifier)\s+['"`]?(\w+)['"`]?/i.test(text) ||
290
- /(?:variable|function|import)\s+['"`]?(\w+)['"`]?\s+(?:is\s+)?(?:defined|declared|imported|available)/i.test(text)) {
291
- return { claim, formallyVerifiable: true, checkType: 'undefined_reference' };
302
+ // Capacity/limit checks — "supports up to N", "limit of N", "maximum N"
303
+ if (/(?:supports?\s+up\s+to|limit\s+(?:of\s+)?\d|maximum\s+(?:of\s+)?\d|up\s+to\s+\d|capacity\s+(?:of\s+)?\d)/i.test(text) ||
304
+ /(?:allows?\s+up\s+to|can\s+(?:hold|handle|store)\s+up\s+to)\s+\d/i.test(text)) {
305
+ return { claim, formallyVerifiable: true, checkType: 'capacity_limit' };
306
+ }
307
+ // Security control checks — encryption, authentication, authorization
308
+ if (/(?:encrypt|decrypt|hashing|hash(?:ed|es|ing)?|ssl|tls|https|cipher)/i.test(text) ||
309
+ /(?:authenticat|two-factor|2fa|mfa|multi-factor|single\s+sign|sso|oauth|saml|jwt)/i.test(text) ||
310
+ /(?:authoriz|access\s+control|permission|role-based|rbac|acl)/i.test(text) ||
311
+ /(?:api\s+key|token|credential|secret|certificate)/i.test(text)) {
312
+ return { claim, formallyVerifiable: true, checkType: 'security_control' };
313
+ }
314
+ // Compliance checks — regulatory compliance references
315
+ if (/(?:compl(?:y|ies|iant|iance)\s+with|gdpr|ccpa|hipaa|soc\s*2|pci[\s-]dss|iso\s*2700|ferpa|coppa)/i.test(text) ||
316
+ /(?:data\s+(?:protection|privacy|retention|deletion)|right\s+to\s+(?:be\s+forgotten|erasure|delete))/i.test(text)) {
317
+ return { claim, formallyVerifiable: true, checkType: 'compliance_check' };
318
+ }
319
+ // Connectivity checks — connections, integrations, webhooks
320
+ if (/(?:connects?\s+(?:to|via|using|with)|websocket|persistent\s+connection|real-time\s+(?:connection|communication))/i.test(text) ||
321
+ /(?:webhook|callback\s+(?:url|endpoint)|event[\s-]driven|pub[\s/]?sub|message\s+(?:queue|broker))/i.test(text) ||
322
+ /(?:integrat(?:es?|ion)\s+with|third[\s-]party\s+(?:api|service|integration))/i.test(text)) {
323
+ return { claim, formallyVerifiable: true, checkType: 'connectivity_check' };
324
+ }
325
+ // Configuration checks — configurable, customizable, settings
326
+ if (/(?:configur(?:able|ation|ed)|customiz(?:able|ation|ed|e)|setting|preference|option(?:al|s)?)/i.test(text) ||
327
+ /(?:adjustable|tuneable|parameteriz|user[\s-]defined|admin[\s-]defined)/i.test(text)) {
328
+ return { claim, formallyVerifiable: true, checkType: 'configuration_check' };
329
+ }
330
+ // Data handling checks — stores, processes, handles, manages data
331
+ if (/(?:stores?|storage|persist(?:s|ed|ent|ence)?|saves?|cach(?:es?|ing|ed))/i.test(text) ||
332
+ /(?:process(?:es|ed|ing)?|transform(?:s|ed|ing)?|convert(?:s|ed|ing)?|pars(?:es?|ing|ed))/i.test(text) ||
333
+ /(?:handles?\s+(?:data|files?|upload|download|request|response|message|event))/i.test(text) ||
334
+ /(?:manages?\s+(?:data|files?|state|session|queue|pool|connection))/i.test(text) ||
335
+ /(?:back(?:up|ed\s+up)|replicate[sd]?|mirror(?:s|ed|ing)?|(?:daily|hourly|weekly)\s+(?:backup|snapshot))/i.test(text) ||
336
+ /(?:retain(?:ed|s)?|retention|ttl|expir(?:es?|ation|y)|purg(?:es?|ing|ed))/i.test(text) ||
337
+ /(?:export(?:able|ed|s)?|delet(?:able|ed?|ion|ing)|remov(?:able|ed?|al|ing))/i.test(text)) {
338
+ return { claim, formallyVerifiable: true, checkType: 'data_handling' };
339
+ }
340
+ // Role/access checks — user roles, permissions, access levels
341
+ // Must come before pricing_billing to avoid "billing" in "excluding billing" matching pricing
342
+ if (/(?:(?:owner|admin|moderator|viewer|editor|manager|operator|developer)\s+role)/i.test(text) ||
343
+ /(?:role\s+(?:has|provides?|grants?|allows?|restricts?))/i.test(text) ||
344
+ /(?:full\s+access|read[\s-]?only\s+(?:access|dashboard)|restricted?\s+(?:to|access))/i.test(text) ||
345
+ /(?:permission|privilege|authorization|access\s+level|scoped?\s+to)/i.test(text)) {
346
+ return { claim, formallyVerifiable: true, checkType: 'role_access' };
347
+ }
348
+ // Pricing/billing checks — costs, prices, subscriptions, billing, refunds
349
+ if (/(?:costs?\s+\$|price[sd]?\s+(?:at|is|are)?\s*\$|\$\d|per\s+month|per\s+year|\/month|\/year|monthly|annually)/i.test(text) ||
350
+ /(?:billed?|billing|subscription|invoice|payment|refund|pro[\s-]rata|trial|free\s+trial)/i.test(text) ||
351
+ /(?:credit\s+card|debit\s+card|ach\s+transfer|usd|dollars?)/i.test(text) ||
352
+ /(?:cancel(?:lation)?|terminate|downgrade|upgrade)\s+(?:their\s+)?(?:subscription|plan|account)/i.test(text)) {
353
+ return { claim, formallyVerifiable: true, checkType: 'pricing_billing' };
354
+ }
355
+ // Media processing checks — image/video/photo processing, resizing, optimization
356
+ if (/(?:resiz(?:e[sd]?|ing)|optimiz(?:e[sd]?|ing|ation)|compress(?:ed|ion|ing)?|thumbnail)/i.test(text) ||
357
+ /(?:exif|gps\s+(?:data|coordinates?)|metadata|orientation|resolution|1080p|4k|hdr)/i.test(text) ||
358
+ /(?:(?:photo|image|video|media)\s+(?:process|analyz|transform|convert|generat|strip))/i.test(text) ||
359
+ /(?:ai\s+(?:moderation|analysis|detection|recognition)|content\s+(?:moderation|analysis|detection|filter))/i.test(text) ||
360
+ /(?:nudity|violence|hate\s+symbols?|inappropriate|offensive)\s+(?:detection|content|categor)/i.test(text) ||
361
+ /(?:confidence\s+score|false\s+positive|false\s+negative|sensitivity\s+threshold)/i.test(text)) {
362
+ return { claim, formallyVerifiable: true, checkType: 'media_processing' };
363
+ }
364
+ // Subjective quality checks — vague/subjective claims about code quality, UX
365
+ if (/(?:highly\s+(?:performant|available|reliable|scalable|secure))/i.test(text) ||
366
+ /(?:intuitive|user[\s-]?friendly|well[\s-]?(?:tested|documented|structured|designed))/i.test(text) ||
367
+ /(?:best\s+practices?|clean\s+code|maintainable|readable)/i.test(text) ||
368
+ /(?:correctly\s+handles?\s+all\s+(?:edge\s+)?cases?|reliable\s+(?:and|under))/i.test(text)) {
369
+ return { claim, formallyVerifiable: true, checkType: 'subjective_quality' };
370
+ }
371
+ // Requirement/constraint checks — must, requires, limited to, within N days
372
+ if (/(?:must\s+(?:be|have|create|meet|complete|provide|verify|include|use))/i.test(text) ||
373
+ /(?:requires?\s+(?:a\s+)?(?:valid|proof|minimum|at\s+least))/i.test(text) ||
374
+ /(?:limited\s+to|restricted\s+to|capped\s+at|within\s+\d+\s+(?:days?|hours?|minutes?|seconds?|months?))/i.test(text) ||
375
+ /(?:(?:is|are)\s+not\s+(?:eligible|allowed|permitted|available))/i.test(text) ||
376
+ /(?:do\s+not\s+require|no\s+(?:credit\s+card|account|login|installation)\s+(?:is\s+)?required)/i.test(text) ||
377
+ /(?:requires?\s+no\s+(?:app|account|login|installation|registration|download))/i.test(text) ||
378
+ /(?:(?:is|are)\s+not\s+(?:sold|shared|distributed|disclosed|transferred))/i.test(text) ||
379
+ /(?:will\s+be\s+(?:disconnected|disabled|suspended|terminated|revoked|removed))/i.test(text) ||
380
+ /(?:after\s+(?:the\s+)?(?:grace|trial|billing)\s+period)/i.test(text)) {
381
+ return { claim, formallyVerifiable: true, checkType: 'requirement_constraint' };
382
+ }
383
+ // Feature presence checks — broadest category, catch-all for "provides", "supports", "includes"
384
+ if (/(?:provides?|supports?|includes?|offers?|enables?|delivers?|features?)/i.test(text) ||
385
+ /(?:allows?\s+(?:users?|customers?|clients?|admins?|operators?)?\s*(?:to\s+)?)/i.test(text) ||
386
+ /(?:(?:users?|customers?|clients?|playlists?|devices?|events?|accounts?)\s+(?:\w+\s+){0,4}(?:may|can|are\s+able\s+to|have\s+the\s+ability|(?:may\s+)?be\s+(?:assigned|created|managed|configured|organized)))/i.test(text) ||
387
+ /(?:accessible|available|capable\s+of|equipped\s+with)/i.test(text) ||
388
+ /(?:receive[sd]?\s+(?:real[\s-]?time|push|email|sms|notification))/i.test(text) ||
389
+ /(?:display[sd]?\s+(?:content|data|information|photos?|images?|video))/i.test(text) ||
390
+ /(?:branded?\s+with|customiz(?:ed?|able)\s+with)/i.test(text) ||
391
+ /(?:have\s+(?:unique|dedicated|custom)\s+(?:\w+\s+)?(?:urls?|qr|codes?|links?|endpoints?))/i.test(text) ||
392
+ /(?:(?:is|are)\s+(?:a\s+)?(?:cloud|web|mobile|desktop|saas|platform))/i.test(text) ||
393
+ /(?:recurring|automatic(?:ally)?|real[\s-]?time|continuous)/i.test(text) ||
394
+ /(?:(?:may|can)\s+(?:submit|manage|restrict|access|control|create|modify|delete|view|export))/i.test(text)) {
395
+ return { claim, formallyVerifiable: true, checkType: 'feature_presence' };
292
396
  }
293
397
  return { claim, formallyVerifiable: false };
294
398
  }
@@ -355,9 +459,28 @@ function checkErrorHandling(code, language) {
355
459
  evidence = 'Error handling found: try/except block';
356
460
  }
357
461
  else if (['go', 'golang'].includes(language)) {
358
- hasErrorHandling = /if\s+err\s*!=\s*nil/.test(clean);
359
- if (hasErrorHandling)
360
- evidence = 'Error handling found: if err != nil check';
462
+ const goPatterns = [
463
+ /if\s+err\s*!=\s*nil/.test(clean),
464
+ /errors\.(?:New|Wrap|Is|As|Unwrap)\s*\(/.test(clean),
465
+ /fmt\.Errorf\s*\(/.test(clean),
466
+ /panic\s*\(/.test(clean),
467
+ /recover\s*\(/.test(clean),
468
+ ];
469
+ hasErrorHandling = goPatterns.some(Boolean);
470
+ if (hasErrorHandling) {
471
+ const mechanisms = [];
472
+ if (goPatterns[0])
473
+ mechanisms.push('if err != nil');
474
+ if (goPatterns[1])
475
+ mechanisms.push('errors package');
476
+ if (goPatterns[2])
477
+ mechanisms.push('fmt.Errorf');
478
+ if (goPatterns[3])
479
+ mechanisms.push('panic');
480
+ if (goPatterns[4])
481
+ mechanisms.push('recover');
482
+ evidence = `Error handling found: ${mechanisms.join(', ')}`;
483
+ }
361
484
  }
362
485
  else if (['java', 'kotlin', 'scala'].includes(language)) {
363
486
  hasErrorHandling = /try\s*\{/.test(clean) && /catch\s*\(/.test(clean);
@@ -365,9 +488,34 @@ function checkErrorHandling(code, language) {
365
488
  evidence = 'Error handling found: try/catch block';
366
489
  }
367
490
  else if (['rust', 'rs'].includes(language)) {
368
- hasErrorHandling = /\?\s*;/.test(clean) || /\.unwrap_or/.test(clean) || /match\s+.*\{[^}]*Err/.test(clean);
369
- if (hasErrorHandling)
370
- evidence = 'Error handling found: Result/? operator or match on Err';
491
+ const rustPatterns = [
492
+ /\?\s*[;,)]/.test(clean),
493
+ /\.unwrap_or(?:_else|_default)?\s*\(/.test(clean),
494
+ /match\s+.*\{[^}]*Err/.test(clean),
495
+ /\.map_err\s*\(/.test(clean),
496
+ /\.ok\(\)\s*\?/.test(clean),
497
+ /\.expect\s*\(/.test(clean),
498
+ /if\s+let\s+(?:Ok|Err|Some|None)\s*\(/.test(clean),
499
+ ];
500
+ hasErrorHandling = rustPatterns.some(Boolean);
501
+ if (hasErrorHandling) {
502
+ const mechanisms = [];
503
+ if (rustPatterns[0])
504
+ mechanisms.push('? operator');
505
+ if (rustPatterns[1])
506
+ mechanisms.push('.unwrap_or*()');
507
+ if (rustPatterns[2])
508
+ mechanisms.push('match on Err');
509
+ if (rustPatterns[3])
510
+ mechanisms.push('.map_err()');
511
+ if (rustPatterns[4])
512
+ mechanisms.push('.ok()?');
513
+ if (rustPatterns[5])
514
+ mechanisms.push('.expect()');
515
+ if (rustPatterns[6])
516
+ mechanisms.push('if let Ok/Err/Some/None');
517
+ evidence = `Error handling found: ${mechanisms.join(', ')}`;
518
+ }
371
519
  }
372
520
  else {
373
521
  hasErrorHandling = /try\s*[{:]/.test(clean) && /catch|except|rescue/.test(clean);
@@ -421,6 +569,36 @@ function checkNullHandling(code, language) {
421
569
  if (hasNullCheck)
422
570
  evidence = 'Null handling found: nil check';
423
571
  }
572
+ else if (['rust', 'rs'].includes(language)) {
573
+ const rustPatterns = [
574
+ /\.is_some\(\)/.test(clean),
575
+ /\.is_none\(\)/.test(clean),
576
+ /\.unwrap_or/.test(clean),
577
+ /if\s+let\s+Some\s*\(/.test(clean),
578
+ /match\s+.*\{[^}]*None/.test(clean),
579
+ /\.map\s*\(/.test(clean) && /Option/.test(clean),
580
+ /\.ok_or/.test(clean),
581
+ ];
582
+ hasNullCheck = rustPatterns.some(Boolean);
583
+ if (hasNullCheck) {
584
+ const mechanisms = [];
585
+ if (rustPatterns[0])
586
+ mechanisms.push('.is_some()');
587
+ if (rustPatterns[1])
588
+ mechanisms.push('.is_none()');
589
+ if (rustPatterns[2])
590
+ mechanisms.push('.unwrap_or*()');
591
+ if (rustPatterns[3])
592
+ mechanisms.push('if let Some');
593
+ if (rustPatterns[4])
594
+ mechanisms.push('match on None');
595
+ if (rustPatterns[5])
596
+ mechanisms.push('Option.map()');
597
+ if (rustPatterns[6])
598
+ mechanisms.push('.ok_or*()');
599
+ evidence = `Null handling found: ${mechanisms.join(', ')}`;
600
+ }
601
+ }
424
602
  else {
425
603
  hasNullCheck = /(?:null|nil|None|undefined|NULL)\s*[!=]/.test(clean) || /[!=]\s*(?:null|nil|None|undefined|NULL)/.test(clean);
426
604
  if (hasNullCheck)
@@ -467,6 +645,60 @@ function checkTypeAnnotations(code, language) {
467
645
  evidence = `Type safety found: ${details.join(', ')}`;
468
646
  }
469
647
  }
648
+ else if (['go', 'golang'].includes(language)) {
649
+ const funcTypes = /func\s+\w+\s*\([^)]*\w+\s+\w+/.test(clean);
650
+ const returnTypes = /\)\s*(?:\(.*\)|[\w*]+)\s*\{/.test(clean);
651
+ const structFields = /\w+\s+(?:string|int|float|bool|byte|error|interface|map|func|chan|\[\]|\*\w)/.test(clean);
652
+ hasTypes = funcTypes || returnTypes || structFields;
653
+ if (hasTypes) {
654
+ const details = [];
655
+ if (funcTypes)
656
+ details.push('typed function parameters');
657
+ if (returnTypes)
658
+ details.push('return type declarations');
659
+ if (structFields)
660
+ details.push('typed struct fields');
661
+ evidence = `Type safety found: ${details.join(', ')}`;
662
+ }
663
+ }
664
+ else if (['rust', 'rs'].includes(language)) {
665
+ const fnTypes = /fn\s+\w+\s*[(<].*->/.test(clean);
666
+ const letTypes = /let\s+(?:mut\s+)?\w+\s*:\s*\w+/.test(clean);
667
+ const structTypes = /struct\s+\w+/.test(clean);
668
+ const traitTypes = /(?:trait|impl)\s+\w+/.test(clean);
669
+ const genericTypes = /<\w+(?:\s*:\s*\w+)?>/.test(clean);
670
+ hasTypes = fnTypes || letTypes || structTypes || traitTypes || genericTypes;
671
+ if (hasTypes) {
672
+ const details = [];
673
+ if (fnTypes)
674
+ details.push('typed function signatures');
675
+ if (letTypes)
676
+ details.push('typed let bindings');
677
+ if (structTypes)
678
+ details.push('struct definitions');
679
+ if (traitTypes)
680
+ details.push('trait/impl definitions');
681
+ if (genericTypes)
682
+ details.push('generic types');
683
+ evidence = `Type safety found: ${details.join(', ')}`;
684
+ }
685
+ }
686
+ else if (['java', 'kotlin', 'scala'].includes(language)) {
687
+ const classTypes = /class\s+\w+/.test(clean);
688
+ const methodTypes = /(?:public|private|protected|static)\s+\w+\s+\w+\s*\(/.test(clean);
689
+ const varTypes = /(?:int|long|double|float|boolean|String|List|Map|Set|char|byte|void)\s+\w+/.test(clean);
690
+ hasTypes = classTypes || methodTypes || varTypes;
691
+ if (hasTypes) {
692
+ const details = [];
693
+ if (classTypes)
694
+ details.push('class definitions');
695
+ if (methodTypes)
696
+ details.push('typed method signatures');
697
+ if (varTypes)
698
+ details.push('typed variable declarations');
699
+ evidence = `Type safety found: ${details.join(', ')}`;
700
+ }
701
+ }
470
702
  else {
471
703
  hasTypes = /:\s*\w+/.test(clean) || /\w+\s+\w+\s*[=(]/.test(clean);
472
704
  evidence = hasTypes ? 'Type annotations detected' : 'No type annotations found';
@@ -553,8 +785,18 @@ function checkSQLParameterized(code, language) {
553
785
  confidence: 1,
554
786
  };
555
787
  }
788
+ // Tagged template SQL libraries (postgres.js, slonik, pglite, sql-template-strings/tag)
789
+ // use ${} as parameterization — not string concatenation. Suppress template literal checks.
790
+ const usesTaggedTemplateSql = /from\s+['"]postgres['"]/.test(codeForSQL) ||
791
+ /require\s*\(\s*['"]postgres['"]\s*\)/.test(codeForSQL) ||
792
+ /from\s+['"]slonik['"]/.test(codeForSQL) ||
793
+ /from\s+['"]@electric-sql\/pglite['"]/.test(codeForSQL) ||
794
+ /from\s+['"]sql-template-strings['"]/.test(codeForSQL) ||
795
+ /from\s+['"]sql-template-tag['"]/.test(codeForSQL) ||
796
+ /postgres\.Sql\b/.test(codeForSQL);
797
+ const templateLiteralConcat = /`[^`]*(?:SELECT|INSERT|UPDATE|DELETE)[^`]*\$\{(?![\d])/i.test(codeForSQL);
556
798
  const stringConcat = /(?:SELECT|INSERT|UPDATE|DELETE)[^'"]*\+\s*\w+/i.test(codeForSQL) ||
557
- /`[^`]*(?:SELECT|INSERT|UPDATE|DELETE)[^`]*\$\{(?![\d])/i.test(codeForSQL) ||
799
+ (templateLiteralConcat && !usesTaggedTemplateSql) || // suppress if tagged template lib
558
800
  /(?:SELECT|INSERT|UPDATE|DELETE)[^'"]*%s/i.test(codeForSQL) ||
559
801
  /(?:SELECT|INSERT|UPDATE|DELETE)[^'"]*\.format\s*\(/i.test(codeForSQL) ||
560
802
  /f['"][^'"]*(?:SELECT|INSERT|UPDATE|DELETE)/i.test(codeForSQL);
@@ -564,7 +806,8 @@ function checkSQLParameterized(code, language) {
564
806
  /:\w+/.test(codeForSQL) ||
565
807
  /\.prepare\s*\(/.test(clean) ||
566
808
  /(?:prisma|drizzle|typeorm|sequelize)\./i.test(clean) ||
567
- /\.query\s*\([^,]+,\s*\[/.test(clean);
809
+ /\.query\s*\([^,]+,\s*\[/.test(clean) ||
810
+ usesTaggedTemplateSql; // tagged template libs ARE parameterized
568
811
  const safe = parameterized && !stringConcat;
569
812
  return {
570
813
  claimId: '',
@@ -589,6 +832,8 @@ function checkInputValidation(code, language) {
589
832
  /if\s*\(\s*!?\s*\w+\s*\|\|\s*typeof/.test(clean),
590
833
  /throw\s+new\s+(?:Error|TypeError|ValidationError|BadRequest)/i.test(clean),
591
834
  /(?:required|minLength|maxLength|min|max|pattern|regex)\s*[:(=]/i.test(clean),
835
+ /\.test\s*\(\s*\w+\s*\)/.test(clean),
836
+ /\.match\s*\(\s*\//.test(clean),
592
837
  ];
593
838
  const hasValidation = patterns.some(Boolean);
594
839
  const mechanisms = [];
@@ -606,6 +851,10 @@ function checkInputValidation(code, language) {
606
851
  mechanisms.push('validation error throwing');
607
852
  if (patterns[7])
608
853
  mechanisms.push('constraint definitions');
854
+ if (patterns[8])
855
+ mechanisms.push('regex .test() validation');
856
+ if (patterns[9])
857
+ mechanisms.push('regex .match() validation');
609
858
  return {
610
859
  claimId: '',
611
860
  checkType: 'input_validation',
@@ -844,6 +1093,299 @@ function checkUndefinedReference(code, language, claim) {
844
1093
  confidence: 1,
845
1094
  };
846
1095
  }
1096
+ // ── Behavioral Claim Verifiers ───────────────────────────────
1097
+ // These verify claims about system behavior by checking code for evidence
1098
+ // of the described functionality. They look for structural patterns that
1099
+ // indicate whether the claimed behavior is implemented.
1100
+ function checkFeaturePresence(code, language, claim) {
1101
+ const clean = preprocessCode(code);
1102
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
1103
+ // Extract key nouns/features from the claim text
1104
+ const featureWords = text
1105
+ .replace(/(?:provides?|supports?|includes?|offers?|enables?|delivers?|features?|allows?|may|can|the|a|an|is|are|with|for|to|of|and|or|in|on|by|via|from|has|have|be|been|that|this|their|its|each|all|any|users?|customers?|clients?|service|system|application|platform|app)\b/gi, '')
1106
+ .trim()
1107
+ .split(/\s+/)
1108
+ .filter(w => w.length > 3);
1109
+ // Check if at least some of the feature-related words appear in the code
1110
+ // Use substring match (not word boundary) to catch camelCase like getAnalytics
1111
+ const matchCount = featureWords.filter(w => {
1112
+ try {
1113
+ return new RegExp(w.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i').test(clean);
1114
+ }
1115
+ catch {
1116
+ return false;
1117
+ }
1118
+ }).length;
1119
+ const matchRatio = featureWords.length > 0 ? matchCount / featureWords.length : 0;
1120
+ // If we find enough related terms in the code, the feature is likely present
1121
+ const hasEvidence = matchRatio >= 0.3 || matchCount >= 2;
1122
+ return {
1123
+ claimId: claim.id,
1124
+ checkType: 'feature_presence',
1125
+ verdict: hasEvidence ? 'PASS' : 'FAIL',
1126
+ evidence: hasEvidence
1127
+ ? `Feature-related terms found in source (${matchCount}/${featureWords.length} terms match)`
1128
+ : `Feature-related terms not found in source (${matchCount}/${featureWords.length} terms match)`,
1129
+ confidence: 1,
1130
+ };
1131
+ }
1132
+ function checkDataHandling(code, language, claim) {
1133
+ const clean = preprocessCode(code);
1134
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
1135
+ const patterns = [
1136
+ { test: /(?:fs\.|readFile|writeFile|createReadStream|createWriteStream)/i.test(clean), label: 'file I/O operations' },
1137
+ { test: /(?:localStorage|sessionStorage|indexedDB|\.setItem|\.getItem)/i.test(clean), label: 'browser storage' },
1138
+ { test: /(?:database|db\.|\.query|\.insert|\.update|\.delete|\.find|\.save|mongoose|prisma|drizzle|knex|sequelize)/i.test(clean), label: 'database operations' },
1139
+ { test: /(?:cache|redis|memcache|lru|\.set\s*\(|\.get\s*\()/i.test(clean), label: 'caching layer' },
1140
+ { test: /(?:upload|download|multer|formidable|busboy|multipart)/i.test(clean), label: 'file upload/download' },
1141
+ { test: /(?:JSON\.parse|JSON\.stringify|serialize|deserialize|marshal|unmarshal)/i.test(clean), label: 'data serialization' },
1142
+ { test: /(?:transform|convert|map|reduce|filter|pipe|stream)/i.test(clean), label: 'data transformation' },
1143
+ { test: /(?:backup|snapshot|replicate|mirror|clone|copy)/i.test(clean), label: 'data backup/replication' },
1144
+ { test: /(?:encrypt|decrypt|hash|hmac|cipher)/i.test(clean), label: 'data encryption' },
1145
+ { test: /(?:queue|worker|job|task|bull|agenda|rabbitmq|kafka)/i.test(clean), label: 'message queue/worker' },
1146
+ ];
1147
+ const found = patterns.filter(p => p.test);
1148
+ const hasDataHandling = found.length > 0;
1149
+ return {
1150
+ claimId: claim.id,
1151
+ checkType: 'data_handling',
1152
+ verdict: hasDataHandling ? 'PASS' : 'FAIL',
1153
+ evidence: hasDataHandling
1154
+ ? `Data handling found: ${found.map(f => f.label).join(', ')}`
1155
+ : 'No data handling patterns found in source code',
1156
+ confidence: 1,
1157
+ };
1158
+ }
1159
+ function checkConfiguration(code, language, claim) {
1160
+ const clean = preprocessCode(code);
1161
+ const patterns = [
1162
+ { test: /(?:config|configuration|settings|preferences|options)\s*[=:{]/i.test(clean), label: 'configuration object' },
1163
+ { test: /(?:\.env|process\.env|import\.meta\.env|os\.environ)/i.test(clean), label: 'environment variables' },
1164
+ { test: /(?:default[A-Z]\w*|DEFAULT_|DEFAULTS)\s*[=:]/i.test(clean), label: 'default values' },
1165
+ { test: /(?:\.config\.|\.settings\.|\.options\.|\.preferences\.)/i.test(clean), label: 'config accessors' },
1166
+ { test: /(?:configurable|customizable|adjustable)/i.test(clean), label: 'configurability markers' },
1167
+ { test: /(?:theme|color|font|style|layout|display)\s*[=:{]/i.test(clean), label: 'display configuration' },
1168
+ { test: /(?:enable|disable|toggle|switch|flag)\s*[=:(]/i.test(clean), label: 'feature flags' },
1169
+ ];
1170
+ const found = patterns.filter(p => p.test);
1171
+ const hasConfig = found.length > 0;
1172
+ return {
1173
+ claimId: claim.id,
1174
+ checkType: 'configuration_check',
1175
+ verdict: hasConfig ? 'PASS' : 'FAIL',
1176
+ evidence: hasConfig
1177
+ ? `Configuration patterns found: ${found.map(f => f.label).join(', ')}`
1178
+ : 'No configuration patterns found in source code',
1179
+ confidence: 1,
1180
+ };
1181
+ }
1182
+ function checkCapacityLimit(code, language, claim) {
1183
+ const clean = preprocessCode(code);
1184
+ const text = `${claim.description} ${claim.assertion}`;
1185
+ // Extract the numeric limit from the claim
1186
+ const limitMatch = text.match(/(?:up\s+to|limit\s+(?:of\s+)?|maximum\s+(?:of\s+)?|capacity\s+(?:of\s+)?)(\d[\d,]*)/i);
1187
+ const limitValue = limitMatch ? limitMatch[1].replace(/,/g, '') : null;
1188
+ const patterns = [
1189
+ { test: /(?:MAX_|LIMIT_|CAPACITY_|MAX[A-Z])\w*\s*[=:]/i.test(clean), label: 'limit constants' },
1190
+ { test: /(?:\.length\s*[<>=]|\.size\s*[<>=]|\.count\s*[<>=])/i.test(clean), label: 'size comparisons' },
1191
+ { test: /(?:quota|throttle|rate[\s_-]?limit|max[\s_-]?(?:size|count|items|users|connections))/i.test(clean), label: 'capacity controls' },
1192
+ { test: limitValue !== null && new RegExp(`\\b${limitValue}\\b`).test(clean), label: `limit value ${limitValue} in code` },
1193
+ ];
1194
+ const found = patterns.filter(p => p.test);
1195
+ const hasLimit = found.length > 0;
1196
+ return {
1197
+ claimId: claim.id,
1198
+ checkType: 'capacity_limit',
1199
+ verdict: hasLimit ? 'PASS' : 'FAIL',
1200
+ evidence: hasLimit
1201
+ ? `Capacity/limit patterns found: ${found.map(f => f.label).join(', ')}`
1202
+ : 'No capacity limit patterns found in source code',
1203
+ confidence: 1,
1204
+ };
1205
+ }
1206
+ function checkSecurityControl(code, language, claim) {
1207
+ const clean = preprocessCode(code);
1208
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
1209
+ const patterns = [
1210
+ { test: /(?:crypto|bcrypt|argon2|scrypt|pbkdf2|sha256|sha512|hmac)/i.test(clean), label: 'cryptographic operations' },
1211
+ { test: /(?:encrypt|decrypt|cipher|aes|rsa)/i.test(clean), label: 'encryption/decryption' },
1212
+ { test: /(?:jwt|jsonwebtoken|passport|oauth|openid)/i.test(clean), label: 'authentication library' },
1213
+ { test: /(?:authorize|permission|role|grant|deny|allow|forbid|access[\s_-]?control)/i.test(clean), label: 'authorization' },
1214
+ { test: /(?:token|bearer|session|cookie|csrf|xsrf)/i.test(clean), label: 'session/token management' },
1215
+ { test: /(?:ssl|tls|https|certificate|cert)/i.test(clean), label: 'TLS/SSL' },
1216
+ { test: /(?:hash|salt|pepper|digest)/i.test(clean), label: 'hashing' },
1217
+ { test: /(?:api[\s_-]?key|secret[\s_-]?key|credentials?)/i.test(clean), label: 'credential management' },
1218
+ { test: /(?:mfa|two[\s_-]?factor|2fa|totp|hotp|otp)/i.test(clean), label: 'multi-factor auth' },
1219
+ { test: /(?:cors|csp|helmet|content[\s_-]?security[\s_-]?policy)/i.test(clean), label: 'security headers' },
1220
+ ];
1221
+ const found = patterns.filter(p => p.test);
1222
+ const hasSecurityControl = found.length > 0;
1223
+ return {
1224
+ claimId: claim.id,
1225
+ checkType: 'security_control',
1226
+ verdict: hasSecurityControl ? 'PASS' : 'FAIL',
1227
+ evidence: hasSecurityControl
1228
+ ? `Security controls found: ${found.map(f => f.label).join(', ')}`
1229
+ : 'No security control patterns found in source code',
1230
+ confidence: 1,
1231
+ };
1232
+ }
1233
+ function checkCompliance(code, language, claim) {
1234
+ const clean = preprocessCode(code);
1235
+ const text = `${claim.description} ${claim.assertion}`.toLowerCase();
1236
+ const patterns = [
1237
+ { test: /(?:gdpr|data[\s_-]?protection|privacy[\s_-]?policy)/i.test(clean), label: 'GDPR/data protection' },
1238
+ { test: /(?:consent|opt[\s_-]?(?:in|out)|cookie[\s_-]?(?:consent|banner|notice))/i.test(clean), label: 'consent management' },
1239
+ { test: /(?:delete[\s_-]?(?:account|data|user)|erase|purge|anonymize|pseudonymize)/i.test(clean), label: 'data deletion/anonymization' },
1240
+ { test: /(?:audit[\s_-]?(?:log|trail)|logging|tracking|compliance)/i.test(clean), label: 'audit logging' },
1241
+ { test: /(?:retention|expir(?:y|ation)|ttl|data[\s_-]?lifecycle)/i.test(clean), label: 'data retention' },
1242
+ { test: /(?:encrypt|hash|mask|redact|sensitive)/i.test(clean), label: 'data protection measures' },
1243
+ { test: /(?:access[\s_-]?(?:log|control)|rbac|permission|role)/i.test(clean), label: 'access controls' },
1244
+ ];
1245
+ const found = patterns.filter(p => p.test);
1246
+ const hasCompliance = found.length > 0;
1247
+ return {
1248
+ claimId: claim.id,
1249
+ checkType: 'compliance_check',
1250
+ verdict: hasCompliance ? 'PASS' : 'FAIL',
1251
+ evidence: hasCompliance
1252
+ ? `Compliance-related patterns found: ${found.map(f => f.label).join(', ')}`
1253
+ : 'No compliance-related patterns found in source code',
1254
+ confidence: 1,
1255
+ };
1256
+ }
1257
+ function checkConnectivity(code, language, claim) {
1258
+ const clean = preprocessCode(code);
1259
+ const patterns = [
1260
+ { test: /(?:websocket|ws\.|wss:|socket\.io|\.on\s*\(\s*['"](?:connect|message|open|close))/i.test(clean), label: 'WebSocket' },
1261
+ { test: /(?:webhook|callback[\s_-]?url|event[\s_-]?(?:handler|listener|emitter))/i.test(clean), label: 'webhook/event handling' },
1262
+ { test: /(?:fetch|axios|got|superagent|request|http\.(?:get|post|put|delete)|XMLHttpRequest)/i.test(clean), label: 'HTTP client' },
1263
+ { test: /(?:mqtt|amqp|rabbitmq|kafka|redis\.(?:pub|sub)|pubsub|nats)/i.test(clean), label: 'message broker' },
1264
+ { test: /(?:grpc|protobuf|graphql|rest[\s_-]?api|api[\s_-]?endpoint)/i.test(clean), label: 'API protocol' },
1265
+ { test: /(?:sse|server[\s_-]?sent|EventSource|text\/event-stream)/i.test(clean), label: 'server-sent events' },
1266
+ { test: /(?:\.connect\s*\(|\.listen\s*\(|createServer|createConnection)/i.test(clean), label: 'connection management' },
1267
+ ];
1268
+ const found = patterns.filter(p => p.test);
1269
+ const hasConnectivity = found.length > 0;
1270
+ return {
1271
+ claimId: claim.id,
1272
+ checkType: 'connectivity_check',
1273
+ verdict: hasConnectivity ? 'PASS' : 'FAIL',
1274
+ evidence: hasConnectivity
1275
+ ? `Connectivity patterns found: ${found.map(f => f.label).join(', ')}`
1276
+ : 'No connectivity patterns found in source code',
1277
+ confidence: 1,
1278
+ };
1279
+ }
1280
+ function checkPricingBilling(code, language, claim) {
1281
+ const clean = preprocessCode(code);
1282
+ const text = `${claim.description} ${claim.assertion}`;
1283
+ // Extract price from claim
1284
+ const priceMatch = text.match(/\$\s*([\d,]+(?:\.\d{2})?)/);
1285
+ const priceValue = priceMatch ? priceMatch[1].replace(/,/g, '') : null;
1286
+ const patterns = [
1287
+ { test: /(?:price|cost|amount|rate|fee|charge)\s*[=:]/i.test(clean), label: 'price definitions' },
1288
+ { test: /(?:stripe|paypal|braintree|paddle|lemon[\s_-]?squeezy|billing)/i.test(clean), label: 'payment provider' },
1289
+ { test: /(?:subscription|plan|tier|pricing)/i.test(clean), label: 'subscription logic' },
1290
+ { test: /(?:invoice|receipt|charge|refund|credit)/i.test(clean), label: 'billing operations' },
1291
+ { test: /(?:trial|free[\s_-]?tier|freemium)/i.test(clean), label: 'trial/free tier' },
1292
+ { test: priceValue !== null && new RegExp(`\\b${priceValue}\\b`).test(clean), label: `price value $${priceValue} in code` },
1293
+ ];
1294
+ const found = patterns.filter(p => p.test);
1295
+ const hasPricing = found.length > 0;
1296
+ return {
1297
+ claimId: claim.id,
1298
+ checkType: 'pricing_billing',
1299
+ verdict: hasPricing ? 'PASS' : 'FAIL',
1300
+ evidence: hasPricing
1301
+ ? `Pricing/billing patterns found: ${found.map(f => f.label).join(', ')}`
1302
+ : 'No pricing/billing patterns found in source code',
1303
+ confidence: 1,
1304
+ };
1305
+ }
1306
+ function checkRoleAccess(code, language, claim) {
1307
+ const clean = preprocessCode(code);
1308
+ const patterns = [
1309
+ { test: /(?:role|permission|privilege|access[\s_-]?level)/i.test(clean), label: 'role/permission definitions' },
1310
+ { test: /(?:admin|moderator|viewer|editor|owner|manager|operator)/i.test(clean), label: 'role names' },
1311
+ { test: /(?:can(?:Access|Edit|Delete|View|Manage)|has(?:Permission|Role|Access))/i.test(clean), label: 'permission checks' },
1312
+ { test: /(?:authorize|isAuthorized|checkPermission|requireRole|guardRole)/i.test(clean), label: 'authorization logic' },
1313
+ { test: /(?:rbac|acl|policy|grant|deny|allow|forbid)/i.test(clean), label: 'access control' },
1314
+ ];
1315
+ const found = patterns.filter(p => p.test);
1316
+ const hasRoleAccess = found.length > 0;
1317
+ return {
1318
+ claimId: claim.id,
1319
+ checkType: 'role_access',
1320
+ verdict: hasRoleAccess ? 'PASS' : 'FAIL',
1321
+ evidence: hasRoleAccess
1322
+ ? `Role/access patterns found: ${found.map(f => f.label).join(', ')}`
1323
+ : 'No role/access patterns found in source code',
1324
+ confidence: 1,
1325
+ };
1326
+ }
1327
+ function checkMediaProcessing(code, language, claim) {
1328
+ const clean = preprocessCode(code);
1329
+ const patterns = [
1330
+ { test: /(?:sharp|jimp|canvas|imagemagick|gm|pillow|opencv)/i.test(clean), label: 'image processing library' },
1331
+ { test: /(?:resize|crop|rotate|flip|thumbnail|scale)/i.test(clean), label: 'image transformation' },
1332
+ { test: /(?:exif|metadata|orientation|gps|geolocation)/i.test(clean), label: 'metadata handling' },
1333
+ { test: /(?:rekognition|vision|detect|moderate|classify|label)/i.test(clean), label: 'AI/ML analysis' },
1334
+ { test: /(?:confidence|score|threshold|probability)/i.test(clean), label: 'confidence scoring' },
1335
+ { test: /(?:upload|download|stream|buffer|blob|file)/i.test(clean), label: 'file handling' },
1336
+ { test: /(?:compress|optimize|quality|format|convert)/i.test(clean), label: 'media optimization' },
1337
+ { test: /(?:ffmpeg|video|audio|transcode|encode|decode)/i.test(clean), label: 'video/audio processing' },
1338
+ ];
1339
+ const found = patterns.filter(p => p.test);
1340
+ const hasMediaProcessing = found.length > 0;
1341
+ return {
1342
+ claimId: claim.id,
1343
+ checkType: 'media_processing',
1344
+ verdict: hasMediaProcessing ? 'PASS' : 'FAIL',
1345
+ evidence: hasMediaProcessing
1346
+ ? `Media processing patterns found: ${found.map(f => f.label).join(', ')}`
1347
+ : 'No media processing patterns found in source code',
1348
+ confidence: 1,
1349
+ };
1350
+ }
1351
+ function checkSubjectiveQuality(code, language, claim) {
1352
+ // Subjective claims cannot be formally verified with high confidence.
1353
+ // We classify them to route them to formal checking, but always PASS
1354
+ // with a note that they require human/LLM judgment.
1355
+ return {
1356
+ claimId: claim.id,
1357
+ checkType: 'subjective_quality',
1358
+ verdict: 'PASS',
1359
+ evidence: 'Subjective quality claim — formal check defers to code review and testing',
1360
+ confidence: 1,
1361
+ };
1362
+ }
1363
+ function checkRequirementConstraint(code, language, claim) {
1364
+ const clean = preprocessCode(code);
1365
+ const text = `${claim.description} ${claim.assertion}`;
1366
+ // Extract numeric constraints from the claim
1367
+ const numericMatch = text.match(/(\d+)\s+(?:characters?|days?|hours?|minutes?|months?|items?|devices?|users?|members?)/i);
1368
+ const numericValue = numericMatch ? numericMatch[1] : null;
1369
+ const patterns = [
1370
+ { test: /(?:require|mandatory|must|shall|enforce)/i.test(clean), label: 'requirement enforcement' },
1371
+ { test: /(?:validate|check|verify|assert|guard)/i.test(clean), label: 'validation logic' },
1372
+ { test: /(?:minimum|maximum|min\b|max\b|limit|constraint|threshold)/i.test(clean), label: 'constraint definitions' },
1373
+ { test: /(?:if\s*\(|throw|reject|deny|forbid|prevent)/i.test(clean), label: 'constraint enforcement' },
1374
+ { test: numericValue !== null && new RegExp(`\\b${numericValue}\\b`).test(clean), label: `constraint value ${numericValue} in code` },
1375
+ { test: /(?:regex|pattern|match|test\s*\()/i.test(clean), label: 'pattern matching' },
1376
+ ];
1377
+ const found = patterns.filter(p => p.test);
1378
+ const hasConstraint = found.length > 0;
1379
+ return {
1380
+ claimId: claim.id,
1381
+ checkType: 'requirement_constraint',
1382
+ verdict: hasConstraint ? 'PASS' : 'FAIL',
1383
+ evidence: hasConstraint
1384
+ ? `Requirement/constraint patterns found: ${found.map(f => f.label).join(', ')}`
1385
+ : 'No requirement/constraint patterns found in source code',
1386
+ confidence: 1,
1387
+ };
1388
+ }
847
1389
  // ── Main Entry Point ─────────────────────────────────────────
848
1390
  export function runFormalVerification(code, language, claims, llmVerifications) {
849
1391
  const classified = claims.map(c => classifyClaim(c));
@@ -885,6 +1427,42 @@ export function runFormalVerification(code, language, claims, llmVerifications)
885
1427
  case 'undefined_reference':
886
1428
  result = checkUndefinedReference(code, language, c.claim);
887
1429
  break;
1430
+ case 'feature_presence':
1431
+ result = checkFeaturePresence(code, language, c.claim);
1432
+ break;
1433
+ case 'data_handling':
1434
+ result = checkDataHandling(code, language, c.claim);
1435
+ break;
1436
+ case 'configuration_check':
1437
+ result = checkConfiguration(code, language, c.claim);
1438
+ break;
1439
+ case 'capacity_limit':
1440
+ result = checkCapacityLimit(code, language, c.claim);
1441
+ break;
1442
+ case 'security_control':
1443
+ result = checkSecurityControl(code, language, c.claim);
1444
+ break;
1445
+ case 'compliance_check':
1446
+ result = checkCompliance(code, language, c.claim);
1447
+ break;
1448
+ case 'connectivity_check':
1449
+ result = checkConnectivity(code, language, c.claim);
1450
+ break;
1451
+ case 'pricing_billing':
1452
+ result = checkPricingBilling(code, language, c.claim);
1453
+ break;
1454
+ case 'role_access':
1455
+ result = checkRoleAccess(code, language, c.claim);
1456
+ break;
1457
+ case 'media_processing':
1458
+ result = checkMediaProcessing(code, language, c.claim);
1459
+ break;
1460
+ case 'subjective_quality':
1461
+ result = checkSubjectiveQuality(code, language, c.claim);
1462
+ break;
1463
+ case 'requirement_constraint':
1464
+ result = checkRequirementConstraint(code, language, c.claim);
1465
+ break;
888
1466
  default:
889
1467
  continue;
890
1468
  }
@@ -932,4 +1510,584 @@ export function runFormalVerification(code, language, claims, llmVerifications)
932
1510
  },
933
1511
  };
934
1512
  }
1513
+ /**
1514
+ * Find async function line ranges by tracking brace depth.
1515
+ * Returns array of [startLine, endLine] (1-indexed).
1516
+ */
1517
+ function findAsyncFunctionRanges(lines) {
1518
+ const ranges = [];
1519
+ let i = 0;
1520
+ while (i < lines.length) {
1521
+ const line = lines[i];
1522
+ // Match async function declarations or async arrow functions assigned to const/let/var
1523
+ if (/\basync\s+function\b/.test(line) || /\basync\s*\(/.test(line) || /\basync\s+\w+\s*=>/.test(line)) {
1524
+ // Find the opening brace on this line or the next few lines
1525
+ let braceStart = -1;
1526
+ let searchLine = i;
1527
+ while (searchLine < Math.min(i + 5, lines.length)) {
1528
+ if (lines[searchLine].includes('{')) {
1529
+ braceStart = searchLine;
1530
+ break;
1531
+ }
1532
+ searchLine++;
1533
+ }
1534
+ if (braceStart === -1) {
1535
+ i++;
1536
+ continue;
1537
+ }
1538
+ // Count braces to find the closing brace
1539
+ let depth = 0;
1540
+ let j = braceStart;
1541
+ while (j < lines.length) {
1542
+ for (const ch of lines[j]) {
1543
+ if (ch === '{')
1544
+ depth++;
1545
+ else if (ch === '}') {
1546
+ depth--;
1547
+ if (depth === 0) {
1548
+ ranges.push([i + 1, j + 1]); // 1-indexed
1549
+ break;
1550
+ }
1551
+ }
1552
+ }
1553
+ if (depth === 0)
1554
+ break;
1555
+ j++;
1556
+ }
1557
+ i = j + 1;
1558
+ }
1559
+ else {
1560
+ i++;
1561
+ }
1562
+ }
1563
+ return ranges;
1564
+ }
1565
+ /**
1566
+ * Scan code line-by-line for issues without requiring a claim object.
1567
+ * Suitable for development-time hooks (pre-commit, editor integration).
1568
+ */
1569
+ export function runClaimlessChecks(code, language) {
1570
+ const findings = [];
1571
+ const lines = code.split('\n');
1572
+ const langLower = language.toLowerCase();
1573
+ const isJsTs = ['js', 'ts', 'jsx', 'tsx', 'javascript', 'typescript'].includes(langLower);
1574
+ // Preprocess code for pattern matching (strips comments/strings)
1575
+ const clean = preprocessCode(code);
1576
+ const cleanLines = clean.split('\n');
1577
+ // ── 1. SQL Injection (string concatenation in SQL context) ───
1578
+ if (isJsTs) {
1579
+ // Look for string concatenation where the string STARTS with a SQL keyword.
1580
+ // This avoids false positives on display strings like "Updated " + count + " records from..."
1581
+ // We check the original line for a string literal starting with SELECT/INSERT/UPDATE/DELETE.
1582
+ for (let i = 0; i < cleanLines.length; i++) {
1583
+ const cl = cleanLines[i];
1584
+ // Must have string concatenation
1585
+ if (/"__STR__"\s*\+\s*\w/.test(cl) ||
1586
+ /\w\s*\+\s*"__STR__"/.test(cl) ||
1587
+ /`__STR__`\s*\+\s*\w/.test(cl) ||
1588
+ /\w\s*\+\s*`__STR__`/.test(cl)) {
1589
+ // Check if the original line has a string that STARTS with a SQL command
1590
+ // e.g., "SELECT * FROM users WHERE id = " + userId
1591
+ const origLine = lines[i] ?? '';
1592
+ if (/['"`]\s*(SELECT|INSERT\s+INTO|UPDATE\s+\w+\s+SET|DELETE\s+FROM)\b/i.test(origLine)) {
1593
+ findings.push({
1594
+ checkType: 'sql_parameterized',
1595
+ line: i + 1,
1596
+ evidence: `SQL query built with string concatenation on line ${i + 1} — use parameterized queries instead`,
1597
+ severity: 'critical',
1598
+ });
1599
+ }
1600
+ }
1601
+ }
1602
+ }
1603
+ // ── 2. API Misuse (KNOWN_BAD_APIS) ───────────────────────────
1604
+ for (const [badApi, info] of Object.entries(KNOWN_BAD_APIS)) {
1605
+ if (info.languages.length === 0)
1606
+ continue;
1607
+ if (!info.languages.includes(langLower))
1608
+ continue;
1609
+ for (let i = 0; i < cleanLines.length; i++) {
1610
+ if (cleanLines[i].includes(badApi)) {
1611
+ findings.push({
1612
+ checkType: 'api_misuse',
1613
+ line: i + 1,
1614
+ evidence: `Line ${i + 1} uses '${badApi.replace(/[()]/g, '')}' which does not exist in ${language}. Use '${info.correct}' instead.`,
1615
+ severity: 'critical',
1616
+ });
1617
+ break; // One finding per bad API pattern
1618
+ }
1619
+ }
1620
+ }
1621
+ // ── 3. Error Handling (async without try/catch) ──────────────
1622
+ if (isJsTs) {
1623
+ const asyncRanges = findAsyncFunctionRanges(lines);
1624
+ for (const [startLine, endLine] of asyncRanges) {
1625
+ // Extract the function body (1-indexed → 0-indexed slice)
1626
+ const bodyLines = cleanLines.slice(startLine - 1, endLine);
1627
+ const bodyText = bodyLines.join('\n');
1628
+ const hasAwait = /\bawait\b/.test(bodyText);
1629
+ const hasTryCatch = /\btry\s*\{/.test(bodyText) && /\}\s*catch\s*\(/.test(bodyText);
1630
+ const hasDotCatch = /\.catch\s*\(/.test(bodyText);
1631
+ if (hasAwait && !hasTryCatch && !hasDotCatch) {
1632
+ findings.push({
1633
+ checkType: 'error_handling',
1634
+ line: startLine,
1635
+ evidence: `Async function starting at line ${startLine} uses 'await' without try/catch error handling`,
1636
+ severity: 'high',
1637
+ });
1638
+ }
1639
+ }
1640
+ }
1641
+ // ── 4. Input Validation (unvalidated req.body/params/query) ──
1642
+ if (isJsTs) {
1643
+ // Detect direct property access from req.body / req.params / req.query
1644
+ // without any validation (zod, joi, express-validator, manual checks)
1645
+ const hasValidation = /(?:z\.|joi\.|Joi\.|validate\(|schema\.|\.parse\(|\.safeParse\(|\.check\(|\.validate\()/.test(clean);
1646
+ if (!hasValidation) {
1647
+ for (let i = 0; i < cleanLines.length; i++) {
1648
+ const cl = cleanLines[i];
1649
+ if (/\breq\.(body|params|query)\b/.test(cl)) {
1650
+ findings.push({
1651
+ checkType: 'input_validation',
1652
+ line: i + 1,
1653
+ evidence: `Line ${i + 1} accesses req.body/params/query without apparent input validation (no zod/joi/validate pattern found)`,
1654
+ severity: 'high',
1655
+ });
1656
+ break; // One finding per file for this pattern
1657
+ }
1658
+ }
1659
+ }
1660
+ }
1661
+ // ── 5. Null Safety (non-null assertions without guard) ───────
1662
+ if (isJsTs) {
1663
+ for (let i = 0; i < cleanLines.length; i++) {
1664
+ const cl = cleanLines[i];
1665
+ // TypeScript non-null assertion !. without a preceding null check
1666
+ if (/\w+!\.\w+/.test(cl)) {
1667
+ // Skip if line also has a null check
1668
+ if (!/(?:\?\?|&&|\?\.)/.test(cl)) {
1669
+ findings.push({
1670
+ checkType: 'null_check',
1671
+ line: i + 1,
1672
+ evidence: `Line ${i + 1} uses non-null assertion (!.) without a null guard — consider optional chaining (?.) instead`,
1673
+ severity: 'medium',
1674
+ });
1675
+ }
1676
+ }
1677
+ }
1678
+ }
1679
+ // ── 6. Security Controls (eval, innerHTML assignment, hardcoded secrets) ──
1680
+ if (isJsTs) {
1681
+ for (let i = 0; i < cleanLines.length; i++) {
1682
+ const cl = cleanLines[i];
1683
+ const orig = lines[i] ?? '';
1684
+ // eval() usage
1685
+ if (/\beval\s*\(/.test(cl)) {
1686
+ findings.push({
1687
+ checkType: 'security_control',
1688
+ line: i + 1,
1689
+ evidence: `Line ${i + 1} uses eval() — arbitrary code execution risk`,
1690
+ severity: 'critical',
1691
+ });
1692
+ continue;
1693
+ }
1694
+ // innerHTML assignment (write, not read)
1695
+ if (/\.innerHTML\s*=/.test(cl)) {
1696
+ findings.push({
1697
+ checkType: 'security_control',
1698
+ line: i + 1,
1699
+ evidence: `Line ${i + 1} assigns to innerHTML — XSS risk, use textContent or a sanitizer instead`,
1700
+ severity: 'critical',
1701
+ });
1702
+ continue;
1703
+ }
1704
+ // Hardcoded secrets — check original line (preprocessCode strips string contents)
1705
+ // Pattern: assignment of a value starting with known secret prefixes, or key=value with secret keyword
1706
+ if (/\bsk_live_[A-Za-z0-9]+/.test(orig) ||
1707
+ /\bsk_test_[A-Za-z0-9]+/.test(orig) ||
1708
+ /(?:api_key|password|secret)\s*[=:]\s*["'`][^"'`]{8,}/.test(orig)) {
1709
+ findings.push({
1710
+ checkType: 'security_control',
1711
+ line: i + 1,
1712
+ evidence: `Line ${i + 1} appears to contain a hardcoded secret or API key — use environment variables instead`,
1713
+ severity: 'critical',
1714
+ });
1715
+ }
1716
+ }
1717
+ }
1718
+ // ── 7. Data Handling (JSON.parse without try/catch) ───────────
1719
+ if (isJsTs) {
1720
+ for (let i = 0; i < cleanLines.length; i++) {
1721
+ if (/\bJSON\.parse\s*\(/.test(cleanLines[i])) {
1722
+ // Check up to 10 lines above for an enclosing try {
1723
+ const windowStart = Math.max(0, i - 10);
1724
+ const context = cleanLines.slice(windowStart, i + 1).join('\n');
1725
+ if (!/\btry\s*\{/.test(context)) {
1726
+ findings.push({
1727
+ checkType: 'data_handling',
1728
+ line: i + 1,
1729
+ evidence: `Line ${i + 1} calls JSON.parse() without a surrounding try/catch — throws on invalid JSON`,
1730
+ severity: 'high',
1731
+ });
1732
+ }
1733
+ }
1734
+ }
1735
+ }
1736
+ // ── 8. Configuration Check (process.env without fallback) ─────
1737
+ if (isJsTs) {
1738
+ for (let i = 0; i < cleanLines.length; i++) {
1739
+ const cl = cleanLines[i];
1740
+ if (/\bprocess\.env\.\w+/.test(cl)) {
1741
+ // Passes if line has ?? or || or ternary (? ... :)
1742
+ if (!/(?:\?\?|\|\||\?\s*[^?])/.test(cl)) {
1743
+ findings.push({
1744
+ checkType: 'configuration_check',
1745
+ line: i + 1,
1746
+ evidence: `Line ${i + 1} reads process.env without a fallback value — may be undefined at runtime`,
1747
+ severity: 'high',
1748
+ });
1749
+ }
1750
+ }
1751
+ }
1752
+ }
1753
+ // ── 9. Undefined References (deprecated package imports) ──────
1754
+ if (isJsTs) {
1755
+ const DEPRECATED_IMPORTS = ['request', 'request-promise', 'node-fetch@2'];
1756
+ for (const pkg of DEPRECATED_IMPORTS) {
1757
+ // Match exact package name in from 'pkg' or require('pkg') — not subpaths like 'request/...'
1758
+ const escapedPkg = pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1759
+ const importPattern = new RegExp(`(?:from\\s+['"]${escapedPkg}['"]|require\\s*\\(\\s*['"]${escapedPkg}['"]\\s*\\))`);
1760
+ for (let i = 0; i < lines.length; i++) {
1761
+ if (importPattern.test(lines[i])) {
1762
+ findings.push({
1763
+ checkType: 'undefined_reference',
1764
+ line: i + 1,
1765
+ evidence: `Line ${i + 1} imports deprecated package '${pkg}' — use a maintained alternative (e.g., node-fetch v3, got, or native fetch)`,
1766
+ severity: 'high',
1767
+ });
1768
+ break; // One finding per deprecated package
1769
+ }
1770
+ }
1771
+ }
1772
+ }
1773
+ // ── 10. Type Annotations (exported functions missing return type) ─
1774
+ // Skip .tsx files entirely — React components conventionally omit return types
1775
+ if (langLower === 'typescript' || langLower === 'ts') {
1776
+ for (let i = 0; i < lines.length; i++) {
1777
+ const orig = lines[i];
1778
+ // Match `export function name(` or `export async function name(`
1779
+ // Skip `export default function` — common React/Next.js pattern that omits return type
1780
+ // Skip Next.js route handlers (GET, POST, PUT, DELETE, PATCH) — conventionally untyped
1781
+ if (/export\s+(?:async\s+)?function\s+\w+\s*\(/.test(orig) &&
1782
+ !/export\s+default\s+(?:async\s+)?function/.test(orig) &&
1783
+ !/export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/.test(orig)) {
1784
+ // Extract the portion from the opening paren to end of line
1785
+ // A return type annotation looks like `): SomeType` after the closing paren
1786
+ // We check if the line (or up to 2 continuation lines) contains `): `
1787
+ const snippet = lines.slice(i, Math.min(i + 3, lines.length)).join(' ');
1788
+ if (!/\)\s*:\s*\w/.test(snippet)) {
1789
+ findings.push({
1790
+ checkType: 'type_annotation',
1791
+ line: i + 1,
1792
+ evidence: `Line ${i + 1} exports a function without an explicit return type annotation`,
1793
+ severity: 'medium',
1794
+ });
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+ // ── 11. Connectivity Check (fetch/axios without timeout) ──────
1800
+ // Use original lines (not cleanLines) because preprocessCode mangles URLs with // in them
1801
+ if (isJsTs) {
1802
+ for (let i = 0; i < lines.length; i++) {
1803
+ const orig = lines[i];
1804
+ if (/\bfetch\s*\(/.test(orig) || /\baxios\b/.test(orig)) {
1805
+ // Check this line plus the next 2 lines for AbortSignal / timeout / signal
1806
+ const window = lines.slice(i, Math.min(i + 3, lines.length)).join('\n');
1807
+ if (!/(?:AbortSignal|timeout|signal)/.test(window)) {
1808
+ findings.push({
1809
+ checkType: 'connectivity_check',
1810
+ line: i + 1,
1811
+ evidence: `Line ${i + 1} makes a network request without a timeout or AbortSignal — may hang indefinitely`,
1812
+ severity: 'medium',
1813
+ });
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ // ── 12. Async forEach (silently drops promises) ──────────────
1819
+ if (isJsTs) {
1820
+ for (let i = 0; i < cleanLines.length; i++) {
1821
+ if (/\.forEach\s*\(\s*async\b/.test(cleanLines[i])) {
1822
+ findings.push({
1823
+ checkType: 'error_handling',
1824
+ line: i + 1,
1825
+ evidence: `Line ${i + 1} passes async callback to forEach — promises are silently dropped. Use for...of with await instead`,
1826
+ severity: 'critical',
1827
+ });
1828
+ }
1829
+ }
1830
+ }
1831
+ // ── 13. Console.log/debug in production code ───────────────
1832
+ if (isJsTs) {
1833
+ // Count total console.log/debug calls in the file
1834
+ // If >5, the file is likely a CLI tool, logger, or test helper — skip entirely
1835
+ const consoleLogCount = cleanLines.filter(l => /\bconsole\.(log|debug)\s*\(/.test(l)).length;
1836
+ if (consoleLogCount <= 5) {
1837
+ for (let i = 0; i < cleanLines.length; i++) {
1838
+ if (/\bconsole\.(log|debug)\s*\(/.test(cleanLines[i])) {
1839
+ findings.push({
1840
+ checkType: 'debug_artifact',
1841
+ line: i + 1,
1842
+ evidence: `Line ${i + 1} has console.log/debug statement — remove before production`,
1843
+ severity: 'medium',
1844
+ });
1845
+ }
1846
+ }
1847
+ }
1848
+ }
1849
+ // ── 14. Loose equality (== null, != undefined) ─────────────
1850
+ if (isJsTs) {
1851
+ for (let i = 0; i < cleanLines.length; i++) {
1852
+ const cl = cleanLines[i];
1853
+ // Skip lines that are entirely comments (preprocessCode empties them)
1854
+ if (/^\s*$/.test(cl))
1855
+ continue;
1856
+ // Use cleanLines to avoid matching inside comments/strings
1857
+ if (/[^!=]==\s*(null|undefined)\b/.test(cl) && !/===\s*(null|undefined)\b/.test(cl)) {
1858
+ findings.push({
1859
+ checkType: 'equality_check',
1860
+ line: i + 1,
1861
+ evidence: `Line ${i + 1} uses loose equality (==) with null/undefined — use strict equality (===) instead`,
1862
+ severity: 'medium',
1863
+ });
1864
+ }
1865
+ // Check for != (but not !==)
1866
+ if (/(?<!=)!=\s*(null|undefined)\b/.test(cl) && !/!==\s*(null|undefined)\b/.test(cl)) {
1867
+ findings.push({
1868
+ checkType: 'equality_check',
1869
+ line: i + 1,
1870
+ evidence: `Line ${i + 1} uses loose inequality (!=) with null/undefined — use strict inequality (!==) instead`,
1871
+ severity: 'medium',
1872
+ });
1873
+ }
1874
+ }
1875
+ }
1876
+ // ── 15. RegExp injection (user input to new RegExp) ────────
1877
+ if (isJsTs) {
1878
+ for (let i = 0; i < cleanLines.length; i++) {
1879
+ const cl = cleanLines[i];
1880
+ // After preprocessCode, string literals become "__STR__"
1881
+ // Flag: new RegExp(variable) where arg is NOT a string literal
1882
+ if (/\bnew\s+RegExp\s*\(/.test(cl) && !/\bnew\s+RegExp\s*\(\s*"__STR__"/.test(cl)) {
1883
+ findings.push({
1884
+ checkType: 'security_control',
1885
+ line: i + 1,
1886
+ evidence: `Line ${i + 1} passes a variable to new RegExp() — user-controlled patterns can cause ReDoS. Escape input or use a literal regex`,
1887
+ severity: 'critical',
1888
+ });
1889
+ }
1890
+ }
1891
+ }
1892
+ // ── 16. Promise.all without error handling ─────────────────
1893
+ if (isJsTs) {
1894
+ for (let i = 0; i < cleanLines.length; i++) {
1895
+ if (/\bPromise\.all\s*\(/.test(cleanLines[i])) {
1896
+ const windowStart = Math.max(0, i - 5);
1897
+ const windowEnd = Math.min(cleanLines.length, i + 6);
1898
+ const context = cleanLines.slice(windowStart, windowEnd).join('\n');
1899
+ const hasCatch = /\.catch\s*\(/.test(context);
1900
+ const hasTryCatch = /\btry\s*\{/.test(context) && /\}\s*catch\s*\(/.test(context);
1901
+ if (!hasCatch && !hasTryCatch) {
1902
+ findings.push({
1903
+ checkType: 'error_handling',
1904
+ line: i + 1,
1905
+ evidence: `Line ${i + 1} uses Promise.all() without error handling — one rejection crashes all. Consider Promise.allSettled() or add .catch()`,
1906
+ severity: 'high',
1907
+ });
1908
+ }
1909
+ }
1910
+ }
1911
+ }
1912
+ // ── 17. parseInt without radix ─────────────────────────────
1913
+ if (isJsTs) {
1914
+ for (let i = 0; i < cleanLines.length; i++) {
1915
+ if (/\bparseInt\s*\(\s*\w[^,)]*\)/.test(cleanLines[i])) {
1916
+ findings.push({
1917
+ checkType: 'api_misuse',
1918
+ line: i + 1,
1919
+ evidence: `Line ${i + 1} calls parseInt() without radix parameter — may produce unexpected results with leading zeros`,
1920
+ severity: 'medium',
1921
+ });
1922
+ }
1923
+ }
1924
+ }
1925
+ // ── 18. Fetch body on GET/HEAD ─────────────────────────────
1926
+ if (isJsTs) {
1927
+ for (let i = 0; i < lines.length; i++) {
1928
+ // Use original lines since preprocessCode strips string contents
1929
+ if (/\bfetch\s*\(/.test(lines[i])) {
1930
+ const windowEnd = Math.min(lines.length, i + 10);
1931
+ const block = lines.slice(i, windowEnd).join('\n');
1932
+ const hasGetOrHead = /method\s*:\s*['"](?:GET|HEAD)['"]/i.test(block);
1933
+ const hasBody = /\bbody\s*:/.test(block);
1934
+ if (hasGetOrHead && hasBody) {
1935
+ findings.push({
1936
+ checkType: 'api_misuse',
1937
+ line: i + 1,
1938
+ evidence: `Line ${i + 1} sends a body with GET/HEAD request — body is ignored by most servers`,
1939
+ severity: 'high',
1940
+ });
1941
+ }
1942
+ }
1943
+ }
1944
+ }
1945
+ // ── 19. addEventListener without cleanup in useEffect ──────
1946
+ if (isJsTs) {
1947
+ const codeText = cleanLines.join('\n');
1948
+ if (/\buseEffect\b/.test(codeText) && /\baddEventListener\b/.test(codeText)) {
1949
+ const addCount = (codeText.match(/\baddEventListener\b/g) || []).length;
1950
+ const removeCount = (codeText.match(/\bremoveEventListener\b/g) || []).length;
1951
+ if (addCount > removeCount) {
1952
+ // Find the first addEventListener line for the line number
1953
+ for (let i = 0; i < cleanLines.length; i++) {
1954
+ if (/\baddEventListener\b/.test(cleanLines[i])) {
1955
+ findings.push({
1956
+ checkType: 'resource_leak',
1957
+ line: i + 1,
1958
+ evidence: `Line ${i + 1} has addEventListener in useEffect without matching removeEventListener — memory leak`,
1959
+ severity: 'high',
1960
+ });
1961
+ break;
1962
+ }
1963
+ }
1964
+ }
1965
+ }
1966
+ }
1967
+ // ── 20. Empty catch block ──────────────────────────────────
1968
+ if (isJsTs) {
1969
+ for (let i = 0; i < cleanLines.length; i++) {
1970
+ const cl = cleanLines[i];
1971
+ // catch (e) {} or catch {} on same line
1972
+ if (/\bcatch\s*(?:\(\s*\w*\s*\))?\s*\{\s*\}/.test(cl)) {
1973
+ findings.push({
1974
+ checkType: 'error_handling',
1975
+ line: i + 1,
1976
+ evidence: `Line ${i + 1} has empty catch block — swallows errors silently. At minimum log the error`,
1977
+ severity: 'high',
1978
+ });
1979
+ continue;
1980
+ }
1981
+ // catch block where { is at end of line and } is on the next line (with only whitespace)
1982
+ if (/\bcatch\s*(?:\(\s*\w*\s*\))?\s*\{\s*$/.test(cl)) {
1983
+ const nextLine = cleanLines[i + 1];
1984
+ if (nextLine !== undefined && /^\s*\}\s*$/.test(nextLine)) {
1985
+ findings.push({
1986
+ checkType: 'error_handling',
1987
+ line: i + 1,
1988
+ evidence: `Line ${i + 1} has empty catch block — swallows errors silently. At minimum log the error`,
1989
+ severity: 'high',
1990
+ });
1991
+ }
1992
+ }
1993
+ }
1994
+ }
1995
+ // ── 21. dangerouslySetInnerHTML ────────────────────────────
1996
+ if (isJsTs) {
1997
+ for (let i = 0; i < cleanLines.length; i++) {
1998
+ if (/\bdangerouslySetInnerHTML\b/.test(cleanLines[i])) {
1999
+ findings.push({
2000
+ checkType: 'security_control',
2001
+ line: i + 1,
2002
+ evidence: `Line ${i + 1} uses dangerouslySetInnerHTML — bypasses React XSS protection. Ensure input is sanitized`,
2003
+ severity: 'critical',
2004
+ });
2005
+ }
2006
+ }
2007
+ }
2008
+ // ── 22. Hardcoded http:// URLs (not localhost) ─────────────
2009
+ if (isJsTs) {
2010
+ for (let i = 0; i < lines.length; i++) {
2011
+ const orig = lines[i];
2012
+ if (/['"`]http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/.test(orig)) {
2013
+ findings.push({
2014
+ checkType: 'security_control',
2015
+ line: i + 1,
2016
+ evidence: `Line ${i + 1} has hardcoded http:// URL — use https:// to prevent mixed content and MITM attacks`,
2017
+ severity: 'medium',
2018
+ });
2019
+ break; // One per file
2020
+ }
2021
+ }
2022
+ }
2023
+ // ── 23. document.write() ───────────────────────────────────
2024
+ if (isJsTs) {
2025
+ for (let i = 0; i < cleanLines.length; i++) {
2026
+ if (/\bdocument\.write\s*\(/.test(cleanLines[i])) {
2027
+ findings.push({
2028
+ checkType: 'security_control',
2029
+ line: i + 1,
2030
+ evidence: `Line ${i + 1} uses document.write() — overwrites page content if called after load. Consider DOM manipulation or iframe-based printing instead`,
2031
+ severity: 'medium',
2032
+ });
2033
+ }
2034
+ }
2035
+ }
2036
+ // ── 24. setTimeout/setInterval with string argument ────────
2037
+ if (isJsTs) {
2038
+ for (let i = 0; i < cleanLines.length; i++) {
2039
+ const cl = cleanLines[i];
2040
+ // After preprocessCode, string literals become "__STR__"
2041
+ if (/\b(?:setTimeout|setInterval)\s*\(\s*"__STR__"/.test(cl)) {
2042
+ findings.push({
2043
+ checkType: 'security_control',
2044
+ line: i + 1,
2045
+ evidence: `Line ${i + 1} passes a string to setTimeout/setInterval — acts as eval(). Use a function instead`,
2046
+ severity: 'critical',
2047
+ });
2048
+ }
2049
+ }
2050
+ }
2051
+ // ── 25. Missing await on fetch() ───────────────────────────
2052
+ if (isJsTs) {
2053
+ for (let i = 0; i < cleanLines.length; i++) {
2054
+ const cl = cleanLines[i];
2055
+ if (/(?:const|let|var)\s+\w+\s*=\s*fetch\s*\(/.test(cl) && !/\bawait\b/.test(cl)) {
2056
+ findings.push({
2057
+ checkType: 'error_handling',
2058
+ line: i + 1,
2059
+ evidence: `Line ${i + 1} assigns fetch() without await — variable holds a Promise, not the Response`,
2060
+ severity: 'high',
2061
+ });
2062
+ }
2063
+ }
2064
+ }
2065
+ // ── 26. Throw string instead of Error ──────────────────────
2066
+ if (isJsTs) {
2067
+ for (let i = 0; i < lines.length; i++) {
2068
+ if (/\bthrow\s+["'`]/.test(lines[i])) {
2069
+ findings.push({
2070
+ checkType: 'error_handling',
2071
+ line: i + 1,
2072
+ evidence: `Line ${i + 1} throws a string literal — stack trace will be lost. Use throw new Error(...) instead`,
2073
+ severity: 'medium',
2074
+ });
2075
+ }
2076
+ }
2077
+ }
2078
+ // ── 27. Array.sort without comparator ──────────────────────
2079
+ if (isJsTs) {
2080
+ for (let i = 0; i < cleanLines.length; i++) {
2081
+ if (/\.sort\s*\(\s*\)/.test(cleanLines[i])) {
2082
+ findings.push({
2083
+ checkType: 'api_misuse',
2084
+ line: i + 1,
2085
+ evidence: `Line ${i + 1} calls .sort() without comparator — converts elements to strings. [10,2,1].sort() gives [1,10,2]`,
2086
+ severity: 'medium',
2087
+ });
2088
+ }
2089
+ }
2090
+ }
2091
+ return findings;
2092
+ }
935
2093
  //# sourceMappingURL=formal-verifier.js.map