@tellescope/sdk 1.256.14 → 1.256.16

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 (293) hide show
  1. package/lib/cjs/sdk.d.ts +2 -2
  2. package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js +1 -1
  3. package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
  4. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
  5. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
  6. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js +388 -0
  7. package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
  8. package/lib/cjs/tests/api_tests/integrations_redacted.test.js +3 -3
  9. package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -1
  10. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +1 -1
  11. package/lib/cjs/tests/api_tests/organization_signup.test.js +1 -1
  12. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
  13. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +103 -162
  14. package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
  15. package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
  16. package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
  17. package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +249 -0
  18. package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
  19. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
  20. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  21. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +281 -0
  22. package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
  23. package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
  24. package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
  25. package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js +390 -0
  26. package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
  27. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
  28. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
  29. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js +1003 -0
  30. package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
  31. package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
  32. package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
  33. package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js +323 -0
  34. package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
  35. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
  36. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
  37. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js +335 -0
  38. package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
  39. package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
  40. package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
  41. package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +237 -0
  42. package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
  43. package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
  44. package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
  45. package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js +349 -0
  46. package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
  47. package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
  48. package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
  49. package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +314 -0
  50. package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
  51. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
  52. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
  53. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js +362 -0
  54. package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
  55. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
  56. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
  57. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js +366 -0
  58. package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
  59. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts +5 -0
  60. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -0
  61. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js +357 -0
  62. package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -0
  63. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
  64. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
  65. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js +248 -0
  66. package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
  67. package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
  68. package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
  69. package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js +138 -0
  70. package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
  71. package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  72. package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  73. package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +201 -0
  74. package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
  75. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
  76. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
  77. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js +278 -0
  78. package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
  79. package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
  80. package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
  81. package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js +155 -0
  82. package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
  83. package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
  84. package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
  85. package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js +88 -0
  86. package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
  87. package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
  88. package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  89. package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +230 -0
  90. package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
  91. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
  92. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  93. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +242 -0
  94. package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
  95. package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
  96. package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
  97. package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js +148 -0
  98. package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
  99. package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
  100. package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
  101. package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js +388 -0
  102. package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
  103. package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
  104. package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
  105. package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js +222 -0
  106. package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
  107. package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  108. package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  109. package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +354 -0
  110. package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  111. package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
  112. package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
  113. package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js +442 -0
  114. package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
  115. package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
  116. package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
  117. package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js +156 -0
  118. package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
  119. package/lib/cjs/tests/api_tests/webhook_error_handling.test.js +2 -2
  120. package/lib/cjs/tests/api_tests/webhook_error_handling.test.js.map +1 -1
  121. package/lib/cjs/tests/tests.d.ts.map +1 -1
  122. package/lib/cjs/tests/tests.js +60 -46
  123. package/lib/cjs/tests/tests.js.map +1 -1
  124. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +1 -1
  125. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +1 -1
  126. package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -0
  127. package/lib/esm/enduser.d.ts +0 -1
  128. package/lib/esm/enduser.d.ts.map +1 -1
  129. package/lib/esm/sdk.d.ts +4 -5
  130. package/lib/esm/sdk.d.ts.map +1 -1
  131. package/lib/esm/session.d.ts +0 -1
  132. package/lib/esm/session.d.ts.map +1 -1
  133. package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js +1 -1
  134. package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
  135. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
  136. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
  137. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js +383 -0
  138. package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
  139. package/lib/esm/tests/api_tests/integrations_redacted.test.js +3 -3
  140. package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -1
  141. package/lib/esm/tests/api_tests/organization_signup.test.d.ts +1 -1
  142. package/lib/esm/tests/api_tests/organization_signup.test.js +1 -1
  143. package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
  144. package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +104 -163
  145. package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
  146. package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
  147. package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
  148. package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +244 -0
  149. package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
  150. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
  151. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  152. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +276 -0
  153. package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
  154. package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
  155. package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
  156. package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js +386 -0
  157. package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
  158. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
  159. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
  160. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js +996 -0
  161. package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
  162. package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
  163. package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
  164. package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js +316 -0
  165. package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
  166. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
  167. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
  168. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js +331 -0
  169. package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
  170. package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
  171. package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
  172. package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +233 -0
  173. package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
  174. package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
  175. package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
  176. package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js +345 -0
  177. package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
  178. package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
  179. package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
  180. package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +310 -0
  181. package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
  182. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
  183. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
  184. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js +358 -0
  185. package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
  186. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
  187. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
  188. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js +362 -0
  189. package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
  190. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts +5 -0
  191. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -0
  192. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js +349 -0
  193. package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js.map +1 -0
  194. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
  195. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
  196. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js +243 -0
  197. package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
  198. package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
  199. package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
  200. package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js +134 -0
  201. package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
  202. package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  203. package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  204. package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +194 -0
  205. package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
  206. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
  207. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
  208. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js +271 -0
  209. package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
  210. package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
  211. package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
  212. package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js +151 -0
  213. package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
  214. package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
  215. package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
  216. package/lib/esm/tests/api_tests/security/prototype-pollution.test.js +84 -0
  217. package/lib/esm/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
  218. package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
  219. package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
  220. package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +223 -0
  221. package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
  222. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
  223. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
  224. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +238 -0
  225. package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
  226. package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
  227. package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
  228. package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js +144 -0
  229. package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
  230. package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
  231. package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
  232. package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js +381 -0
  233. package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
  234. package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
  235. package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
  236. package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js +218 -0
  237. package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
  238. package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  239. package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  240. package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +350 -0
  241. package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  242. package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
  243. package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
  244. package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js +435 -0
  245. package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
  246. package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
  247. package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
  248. package/lib/esm/tests/api_tests/security/webhook-timeout.test.js +149 -0
  249. package/lib/esm/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
  250. package/lib/esm/tests/api_tests/webhook_error_handling.test.js +2 -2
  251. package/lib/esm/tests/api_tests/webhook_error_handling.test.js.map +1 -1
  252. package/lib/esm/tests/tests.d.ts.map +1 -1
  253. package/lib/esm/tests/tests.js +38 -24
  254. package/lib/esm/tests/tests.js.map +1 -1
  255. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +1 -1
  256. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +1 -1
  257. package/lib/tsconfig.tsbuildinfo +1 -1
  258. package/package.json +10 -10
  259. package/src/tests/api_tests/auto_merge_form_submission.test.ts +1 -1
  260. package/src/tests/api_tests/enduser_observations_auto_review.test.ts +358 -0
  261. package/src/tests/api_tests/integrations_redacted.test.ts +1 -1
  262. package/src/tests/api_tests/organization_signup.test.ts +2 -2
  263. package/src/tests/api_tests/purchase_made_trigger.test.ts +29 -41
  264. package/src/tests/api_tests/security/{F-0196-add-to-journey-foreign-enduserid.test.ts → add-to-journey-foreign-enduserid.test.ts} +6 -7
  265. package/src/tests/api_tests/security/{F-0005-ai-conversations-rbac.test.ts → ai-conversations-rbac.test.ts} +14 -14
  266. package/src/tests/api_tests/security/{F-0166-allowed-paths-wildcard-bypass.test.ts → allowed-paths-confinement.test.ts} +11 -11
  267. package/src/tests/api_tests/security/{F-0192-automated-actions-process-gate.test.ts → automated-actions-process-gate.test.ts} +6 -7
  268. package/src/tests/api_tests/security/{F-0179-booking-info-idor-token-mint.test.ts → booking-info-idor-token-mint.test.ts} +5 -6
  269. package/src/tests/api_tests/security/{F-0053-cascade-role-rename-cross-tenant.test.ts → cascade-role-rename-cross-tenant.test.ts} +10 -10
  270. package/src/tests/api_tests/security/{F-0001-data-sync-redaction-bypass.test.ts → data-sync-redaction-bypass.test.ts} +9 -9
  271. package/src/tests/api_tests/security/{F-0006-develop-health-benefit-verification-rbac.test.ts → develop-health-benefit-verification-rbac.test.ts} +7 -8
  272. package/src/tests/api_tests/security/{F-0116-F-0119-enduser-cross-create.test.ts → enduser-cross-create.test.ts} +25 -25
  273. package/src/tests/api_tests/security/{F-0106-F-0110-enduser-write-restrictions.test.ts → enduser-write-restrictions.test.ts} +12 -14
  274. package/src/tests/api_tests/security/file-link-session-confinement.test.ts +291 -0
  275. package/src/tests/api_tests/security/form-logic-js-execution.test.ts +183 -0
  276. package/src/tests/api_tests/security/{F-0145-globalunique-leak.test.ts → globalunique-conflict-redaction.test.ts} +5 -6
  277. package/src/tests/api_tests/security/{F-0008-handle-incoming-communication-cross-tenant.test.ts → handle-incoming-communication-cross-tenant.test.ts} +8 -8
  278. package/src/tests/api_tests/security/{F-0007-invite-user-enumeration.test.ts → invite-user-enumeration.test.ts} +8 -8
  279. package/src/tests/api_tests/security/{F-0151-load-inbox-redaction.test.ts → load-inbox-redaction.test.ts} +5 -6
  280. package/src/tests/api_tests/security/{F-0016-prototype-pollution.test.ts → prototype-pollution.test.ts} +4 -4
  281. package/src/tests/api_tests/security/{F-0025-proxy-image-unauth-ssrf.test.ts → proxy-image-unauth-ssrf.test.ts} +11 -12
  282. package/src/tests/api_tests/security/{F-0013-sanitize-user-html-with-iframes.test.ts → sanitize-user-html-with-iframes.test.ts} +5 -5
  283. package/src/tests/api_tests/security/{F-0013-sanitize-user-html.test.ts → sanitize-user-html.test.ts} +4 -4
  284. package/src/tests/api_tests/security/{F-0191-schema-adminonly-enforcement.test.ts → schema-adminonly-enforcement.test.ts} +6 -7
  285. package/src/tests/api_tests/security/{F-0076-self-admin-role-assignment.test.ts → self-admin-role-assignment.test.ts} +13 -13
  286. package/src/tests/api_tests/security/{F-0185-startlinktoken-enduser-staff-escalation.test.ts → startlinktoken-enduser-staff-escalation.test.ts} +6 -6
  287. package/src/tests/api_tests/security/{F-0015-webhook-outbound-ssrf.test.ts → webhook-outbound-ssrf.test.ts} +13 -14
  288. package/src/tests/api_tests/security/{F-0155-webhook-timeout.test.ts → webhook-timeout.test.ts} +6 -7
  289. package/src/tests/api_tests/webhook_error_handling.test.ts +1 -1
  290. package/src/tests/tests.ts +43 -24
  291. package/src/tests/unit_tests/openapi_spec_freshness.test.ts +1 -1
  292. package/test_generated.pdf +0 -0
  293. package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +0 -301
@@ -0,0 +1,291 @@
1
+ require('source-map-support').install();
2
+
3
+ import axios from "axios"
4
+
5
+ import {
6
+ assert_checks,
7
+ Check,
8
+ setup_isolated_tenant,
9
+ } from "../../setup_isolated"
10
+
11
+ const host = process.env.API_URL || 'http://localhost:8080' as const
12
+
13
+ const RAND = () => Math.random().toString(36).slice(2, 10)
14
+
15
+ /**
16
+ * The `{{files.<id>.link}}` merge field must mint a session that can download ONE file, and nothing
17
+ * else.
18
+ *
19
+ * That merge field is how the webapp's "Generate PDF -> Save and Share -> Email" flow on the enduser
20
+ * timeline (DownloadFormResponse.tsx, ComposeSharePDFEmail) delivers a document to a hand-typed
21
+ * address — a referral contact, a caregiver, an outside provider. The link is resolved at send time
22
+ * by message_templating.ts, which called login_enduser with no confinement at all, so the recipient
23
+ * received a full 24-hour patient-portal session: chat history, every form response, every file,
24
+ * observations, medications, and every write route including PATCH /enduser/:id.
25
+ *
26
+ * Route access was not the only leak. Both portals call refresh-enduser-session during URL-token
27
+ * intake, and for a session not flagged `fromPublicSession` that handler returns the whole patient
28
+ * document (name, dateOfBirth, email, phone, insurance, fields, notes) — so the chart was handed
29
+ * over on page load, before a single route was touched. Probe 7b covers that.
30
+ *
31
+ * Two variants, because one bit — is the recipient the patient? — drives two behaviours the merge
32
+ * field previously could not express:
33
+ *
34
+ * `link` goes to the patient. Keeps the org's MFA policy, keeps OTP recovery.
35
+ * `shared-link` goes to someone who is NOT the patient. An OTP code is delivered to the PATIENT's
36
+ * own email or phone, so for this variant recovery is both unanswerable by the
37
+ * holder and a way to make the platform message the patient on demand. It is
38
+ * refused, and the token is minted bypassMFA so it never needs a challenge either.
39
+ *
40
+ * Probe 12 is the one that decides whether the OTP rule is real: the three OTP endpoints read their
41
+ * `token` argument with `jwtDecode`, which does not verify the signature. The file link puts a
42
+ * decodable JWT in the recipient's URL bar, so they know the enduser id, and a scope name in an
43
+ * unverified token is a suggestion rather than a claim. Without signature verification the rule is
44
+ * a paper wall.
45
+ *
46
+ * Every confinement probe is paired against the tenant's own admin session, so a red check means
47
+ * confinement rather than a broken fixture.
48
+ */
49
+
50
+ type Probe = { status: number, body: any }
51
+
52
+ const probe = async (
53
+ method: 'get' | 'post' | 'patch',
54
+ path: string,
55
+ o: { token?: string, params?: object, data?: object } = {},
56
+ ): Promise<Probe> => {
57
+ const r = await axios.request({
58
+ method,
59
+ url: `${host}/v1${path}`,
60
+ params: o.params,
61
+ data: o.data,
62
+ headers: o.token ? { Authorization: `Bearer ${o.token}` } : undefined,
63
+ validateStatus: () => true,
64
+ })
65
+ return { status: r.status, body: r.data }
66
+ }
67
+
68
+ export const file_link_session_confinement_checks = async (): Promise<Check[]> => {
69
+ const checks: Check[] = []
70
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
71
+
72
+ let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
73
+
74
+ try {
75
+ tenant = await setup_isolated_tenant({
76
+ name: `FileLink ${RAND()}`, users: [{ isAdmin: true }], apiKeys: 0,
77
+ })
78
+ const sdk = tenant.sdk
79
+
80
+ // dontsend- so the OTP probes cannot deliver anything real, same convention the provisioner uses
81
+ const enduser = await sdk.api.endusers.createOne({
82
+ fname: 'FileLink', lname: 'Recipient',
83
+ email: `dontsend-filelink-${RAND()}@tellescope.com`,
84
+ phone: '+15555550100',
85
+ dateOfBirth: '01-01-1990',
86
+ })
87
+
88
+ // No S3 upload needed: prepare_file_upload inserts the files record with its secureName
89
+ // immediately, and file_download_URL only SIGNS a GET url — it never checks the object exists.
90
+ const newFile = async (name: string) => (
91
+ (await sdk.api.files.prepare_file_upload({
92
+ name, type: 'application/pdf', size: 1000, enduserId: enduser.id,
93
+ })).file
94
+ )
95
+ const fileA = await newFile(`shared-${RAND()}.pdf`) // the one that gets shared
96
+ const fileB = await newFile(`private-${RAND()}.pdf`) // the rest of the patient's chart
97
+
98
+ /**
99
+ * Mints a token exactly the way a real send does — get_templated_message is the endpoint the
100
+ * worker calls (worker/app.js handleOutgoingEmail).
101
+ *
102
+ * replaceLinks always rewrites the resolved link into a tracking redirect, so `plaintext` holds
103
+ * `doc (${API_URL}/r/<redirectToken>)` rather than the portal URL. Follow the 302 to recover it.
104
+ */
105
+ const mint = async (verb: 'link' | 'shared-link') => {
106
+ const { plaintext } = await sdk.api.templates.get_templated_message({
107
+ channel: 'Email',
108
+ userId: tenant!.users[0].id,
109
+ enduserId: enduser.id,
110
+ message: `{{files.${fileA.id}.${verb}:doc}}`,
111
+ })
112
+
113
+ const redirectURL = plaintext.match(/\((https?:\/\/[^)\s]+)\)/)?.[1]
114
+ if (!redirectURL) return { token: undefined, detail: `no link resolved from: ${plaintext}` }
115
+
116
+ const r = await axios.get(redirectURL, { maxRedirects: 0, validateStatus: () => true })
117
+ const location = r.headers['location']
118
+ if (!location) return { token: undefined, detail: `redirect did not 302 (${r.status})` }
119
+
120
+ const token = new URL(location).searchParams.get('token') ?? undefined
121
+ return { token, detail: token ? 'minted' : `no token in ${location}` }
122
+ }
123
+
124
+ /* ================================ confinement + the pin ================================ */
125
+
126
+ // Positive control first: the admin session reads both files fine, so a 403 below is confinement.
127
+ const adminFiles = await probe('get', '/files', { token: tenant.users[0].authToken })
128
+ record(
129
+ 'control: admin session lists files',
130
+ adminFiles.status === 200, `status ${adminFiles.status}`,
131
+ )
132
+
133
+ const confinement_probes = async (variant: 'link' | 'shared-link', token: string) => {
134
+ const p = (label: string, expected: number, r: Probe) => record(
135
+ `${variant} — ${label}`, r.status === expected, `status ${r.status} (expected ${expected})`,
136
+ )
137
+
138
+ p('1: downloads the shared file', 200,
139
+ await probe('get', '/file-download-URL', { token, params: { secureName: fileA.secureName } }))
140
+
141
+ p('2: cannot download another file on the same patient', 403,
142
+ await probe('get', '/file-download-URL', { token, params: { secureName: fileB.secureName } }))
143
+
144
+ // validateInput merges query, then body, then params — body wins over query, so a pin checked
145
+ // against req.query rather than req.validated would be sidesteppable exactly like this.
146
+ p('3: cannot smuggle another secureName through the body', 403,
147
+ await probe('get', '/file-download-URL', {
148
+ token, params: { secureName: fileA.secureName }, data: { secureName: fileB.secureName },
149
+ }))
150
+
151
+ // a listing would turn the pin into a directory of the patient's chart
152
+ p('4: cannot list files', 403, await probe('get', '/files', { token }))
153
+
154
+ p('5a: cannot read chats', 403, await probe('get', '/chats', { token }))
155
+ p('5b: cannot read form responses', 403, await probe('get', '/form-responses', { token }))
156
+ p('5c: cannot read observations', 403, await probe('get', '/enduser-observations', { token }))
157
+
158
+ p('6: cannot update the patient', 403,
159
+ await probe('patch', `/enduser/${enduser.id}`, { token, data: { updates: { fname: 'Overwritten' } } }))
160
+
161
+ const refreshed = await probe('post', '/refresh-enduser-session', { token })
162
+ p('7: can refresh its own session', 200, refreshed)
163
+
164
+ // 7b — the refresh payload must be the fromPublicSession shape, not the whole chart
165
+ const leaked = ['dateOfBirth', 'email', 'phone', 'fields', 'insurance', 'lname']
166
+ .filter(f => (refreshed.body?.enduser ?? {})[f] !== undefined)
167
+ record(
168
+ `${variant} — 7b: refresh response withholds patient fields`,
169
+ refreshed.status === 200 && leaked.length === 0,
170
+ leaked.length ? `leaked: ${leaked.join(', ')}` : 'no patient fields in payload',
171
+ )
172
+
173
+ // 8 — confinement has to survive the re-mint, or one refresh launders it away
174
+ const refreshedToken = refreshed.body?.authToken
175
+ if (typeof refreshedToken === 'string' && refreshedToken) {
176
+ p('8a: refreshed token still cannot download another file', 403,
177
+ await probe('get', '/file-download-URL', {
178
+ token: refreshedToken, params: { secureName: fileB.secureName },
179
+ }))
180
+ p('8b: refreshed token still cannot update the patient', 403,
181
+ await probe('patch', `/enduser/${enduser.id}`, {
182
+ token: refreshedToken, data: { updates: { fname: 'Overwritten' } },
183
+ }))
184
+ } else {
185
+ record(`${variant} — 8: refreshed token available`, false, 'refresh returned no authToken')
186
+ }
187
+ }
188
+
189
+ const plain = await mint('link')
190
+ if (plain.token) {
191
+ await confinement_probes('link', plain.token)
192
+ } else {
193
+ record('link — minted', false, plain.detail)
194
+ }
195
+
196
+ const shared = await mint('shared-link')
197
+ if (shared.token) {
198
+ await confinement_probes('shared-link', shared.token)
199
+ } else {
200
+ // Before the shared-link verb exists the merge field does not resolve. Record it and skip the
201
+ // shared probes rather than throwing, so the first run reports the whole picture at once.
202
+ record('shared-link — minted', false, shared.detail)
203
+ }
204
+
205
+ /* ========================================= OTP ========================================= */
206
+
207
+ if (shared.token) {
208
+ const methods = await probe('get', '/endusers/otp-methods', { params: { token: shared.token } })
209
+ record(
210
+ 'shared-link — 9: OTP methods are not offered',
211
+ methods.status === 403, `status ${methods.status} (expected 403)`,
212
+ )
213
+
214
+ const send = await probe('post', '/endusers/send-otp-code', {
215
+ data: { token: shared.token, method: 'email' },
216
+ })
217
+ record(
218
+ 'shared-link — 10: cannot make us send the patient a login code',
219
+ send.status === 403, `status ${send.status} (expected 403)`,
220
+ )
221
+
222
+ // rejected on scope BEFORE the code lookup, so it is not an oracle on code validity
223
+ const verify = await probe('post', '/endusers/verify-otp-code', {
224
+ data: { token: shared.token, code: '000000' },
225
+ })
226
+ record(
227
+ 'shared-link — 11: OTP verification refuses the scope before checking the code',
228
+ verify.status === 403, `status ${verify.status} (expected 403, 400 = reached the code check)`,
229
+ )
230
+
231
+ // 12 — the same call with the scope claim stripped and the signature broken. The OTP endpoints
232
+ // jwtDecode without verifying, so without signature verification this sails straight through
233
+ // and every check above is decorative.
234
+ const [header, payload] = shared.token.split('.')
235
+ const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString())
236
+ delete decoded.sessionScopes
237
+ delete decoded.scopeContext
238
+ const forged = [
239
+ header,
240
+ Buffer.from(JSON.stringify(decoded)).toString('base64url'),
241
+ 'not-a-real-signature',
242
+ ].join('.')
243
+
244
+ const forgedSend = await probe('post', '/endusers/send-otp-code', {
245
+ data: { token: forged, method: 'email' },
246
+ })
247
+ record(
248
+ 'shared-link — 12: a forged token cannot strip the scope to unlock OTP',
249
+ forgedSend.status === 403 || forgedSend.status === 401 || forgedSend.status === 404,
250
+ `status ${forgedSend.status} (expected 401/403/404; 204 = signature never verified)`,
251
+ )
252
+ }
253
+
254
+ if (plain.token) {
255
+ // 13 — the patient-facing variant must keep its recovery path. Deliverable to a dontsend-
256
+ // address, so nothing leaves the system.
257
+ const send = await probe('post', '/endusers/send-otp-code', {
258
+ data: { token: plain.token, method: 'email' },
259
+ })
260
+ record(
261
+ 'link — 13: patient OTP recovery still works',
262
+ send.status === 204, `status ${send.status} (expected 204)`,
263
+ )
264
+ }
265
+ } finally {
266
+ // Teardown BEFORE anything asserts — assert calls process.exit, which skips pending finallys and
267
+ // would strand the provisioned tenant permanently.
268
+ if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
269
+ }
270
+
271
+ return checks
272
+ }
273
+
274
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
275
+ export const file_link_session_confinement_tests = async () => {
276
+ assert_checks('Shared file link session confinement', await file_link_session_confinement_checks())
277
+ }
278
+
279
+ // Allow running this test file independently
280
+ if (require.main === module) {
281
+ console.log(`🌐 Using API URL: ${host}`)
282
+ file_link_session_confinement_tests()
283
+ .then(() => {
284
+ console.log("✅ file link session confinement test suite completed successfully")
285
+ process.exit(0)
286
+ })
287
+ .catch((error) => {
288
+ console.error("❌ file link session confinement test suite failed:", error)
289
+ process.exit(1)
290
+ })
291
+ }
@@ -0,0 +1,183 @@
1
+ require('source-map-support').install();
2
+
3
+ import {
4
+ assert_checks,
5
+ Check,
6
+ setup_isolated_tenant,
7
+ } from "../../setup_isolated"
8
+
9
+ const host = process.env.API_URL || 'http://localhost:8080' as const
10
+
11
+ const RAND = () => Math.random().toString(36).slice(2, 10)
12
+
13
+ /**
14
+ * Regression test: `$JS(...)` must be rejected in stored conditional logic.
15
+ *
16
+ * `responses_satisfy_conditions` (utilities/src/utils.ts) compiled a stored condition value of the
17
+ * form `$JS(...)` with `new Function()` and executed it — in every form-filler's browser, including
18
+ * anonymous visitors on public intake pages, AND inside the API process (automation_triggers.ts
19
+ * trigger matching on every form submission, ai_agent/form_filling.ts voice-agent navigation). The
20
+ * API process holds every tenant's database handle, so a condition authored by staff in one tenant
21
+ * reached all of them. Any staff user or API key with form_fields write access could plant one by
22
+ * typing into the free-text $gt/$lt box — no API call required.
23
+ *
24
+ * The sink is removed. This asserts the second half of the fix: `compoundFilterValidator` rejects the
25
+ * syntax at write time on every field that feeds that evaluator, so it cannot be reintroduced through
26
+ * stored configuration. The runtime half — that a `$JS` value is never evaluated — is covered by the
27
+ * pure-function cases in `form_conditional_logic_tests` in tests.ts.
28
+ *
29
+ * The five condition locations were established by enumerating every `responses_satisfy_conditions`
30
+ * call site, and all five are covered here. That completeness matters: the first run of this test
31
+ * caught a half fix. `event.conditions` is wrapped in an `orValidator`, which returns the first arm
32
+ * that does not throw, and its permissive `optional: optionalAnyObjectValidator` arm was declared
33
+ * before the guarded one — so the payload was accepted and stored even though the validator had been
34
+ * swapped. Pointing a field at the guard is not the same as the guard being reachable, which is why
35
+ * every location gets an assertion rather than an argument.
36
+ *
37
+ * Each rejection is paired with a positive control using the same shape without `$JS(`, so a passing
38
+ * test cannot be explained by the write failing for an unrelated reason.
39
+ */
40
+ export const form_logic_js_execution_checks = async (): Promise<Check[]> => {
41
+ const checks: Check[] = []
42
+ const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
43
+
44
+ const JS_PAYLOAD = '$JS(return 0)'
45
+ const SAFE_VALUE = '18'
46
+
47
+ const condition = (value: string) => ({ $and: [{ condition: { 'someFieldId': { $gt: value } } }] })
48
+ const previousFields = (value: string) => ([{
49
+ type: 'compoundLogic',
50
+ info: { fieldId: '000000000000000000000000', priority: 5, label: '', condition: condition(value) },
51
+ }])
52
+
53
+ const attempt = async (fn: () => Promise<any>) => (
54
+ fn().then(() => ({ accepted: true, detail: 'accepted' }))
55
+ .catch((e: any) => ({ accepted: false, detail: `rejected: ${e?.message || e}` }))
56
+ )
57
+
58
+ let tenant: Awaited<ReturnType<typeof setup_isolated_tenant>> | undefined
59
+
60
+ try {
61
+ tenant = await setup_isolated_tenant({ name: `F0200 ${RAND()}`, users: [{ isAdmin: true }], apiKeys: 0 })
62
+ const sdk = tenant.sdk
63
+
64
+ const form = await sdk.api.forms.createOne({ title: `${RAND()}` })
65
+ const field = await sdk.api.form_fields.createOne({
66
+ formId: form.id, title: 'anchor', type: 'string', previousFields: [{ type: 'root', info: {} }],
67
+ })
68
+
69
+ /* ===== 1. previousFields[].info.condition — the field the finding was reported against ===== */
70
+
71
+ const controlCreate = await attempt(() => sdk.api.form_fields.createOne({
72
+ formId: form.id, title: 'control', type: 'string',
73
+ previousFields: previousFields(SAFE_VALUE) as any,
74
+ }))
75
+ record(
76
+ 'control: form_fields.createOne accepts a normal $gt condition',
77
+ controlCreate.accepted, controlCreate.detail,
78
+ )
79
+
80
+ const payloadCreate = await attempt(() => sdk.api.form_fields.createOne({
81
+ formId: form.id, title: 'payload', type: 'string',
82
+ previousFields: previousFields(JS_PAYLOAD) as any,
83
+ }))
84
+ record(
85
+ '1a: form_fields.createOne rejects $JS( in previousFields condition',
86
+ !payloadCreate.accepted, payloadCreate.detail,
87
+ )
88
+
89
+ const payloadUpdate = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
90
+ previousFields: previousFields(JS_PAYLOAD) as any,
91
+ }))
92
+ record(
93
+ '1b: form_fields.updateOne rejects $JS( in previousFields condition',
94
+ !payloadUpdate.accepted, payloadUpdate.detail,
95
+ )
96
+
97
+ /* ===== 2. groupShowCondition ===== */
98
+
99
+ const groupControl = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
100
+ groupShowCondition: condition(SAFE_VALUE) as any,
101
+ }))
102
+ record('control: groupShowCondition accepts a normal condition', groupControl.accepted, groupControl.detail)
103
+
104
+ const groupPayload = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
105
+ groupShowCondition: condition(JS_PAYLOAD) as any,
106
+ }))
107
+ record('2: form_fields.updateOne rejects $JS( in groupShowCondition', !groupPayload.accepted, groupPayload.detail)
108
+
109
+ /* ===== 3. options.optionDetails[].showCondition ===== */
110
+
111
+ const optionControl = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
112
+ options: { optionDetails: [{ option: 'A', showCondition: condition(SAFE_VALUE) }] } as any,
113
+ }))
114
+ record('control: optionDetails showCondition accepts a normal condition', optionControl.accepted, optionControl.detail)
115
+
116
+ const optionPayload = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
117
+ options: { optionDetails: [{ option: 'A', showCondition: condition(JS_PAYLOAD) }] } as any,
118
+ }))
119
+ record('3: form_fields.updateOne rejects $JS( in optionDetails showCondition', !optionPayload.accepted, optionPayload.detail)
120
+
121
+ /* ===== 4. options.productConditions[].showCondition ===== */
122
+
123
+ const productControl = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
124
+ options: { productConditions: [{ productId: '000000000000000000000000', showCondition: condition(SAFE_VALUE) }] } as any,
125
+ }))
126
+ record('control: productConditions showCondition accepts a normal condition', productControl.accepted, productControl.detail)
127
+
128
+ const productPayload = await attempt(() => sdk.api.form_fields.updateOne(field.id, {
129
+ options: { productConditions: [{ productId: '000000000000000000000000', showCondition: condition(JS_PAYLOAD) }] } as any,
130
+ }))
131
+ record('4: form_fields.updateOne rejects $JS( in productConditions showCondition', !productPayload.accepted, productPayload.detail)
132
+
133
+ /* ===== 5. automation_triggers — the server-side evaluation path ===== */
134
+
135
+ const trigger = (conditions: object, extraInfo: object = {}) => ({
136
+ title: `${RAND()}`,
137
+ status: 'Inactive',
138
+ event: { type: 'Form Submitted', info: { formId: form.id, ...extraInfo }, ...conditions },
139
+ action: { type: 'Add Tags', info: { tags: ['security-regression'] } },
140
+ } as any)
141
+
142
+ const triggerControl = await attempt(() => sdk.api.automation_triggers.createOne(
143
+ trigger({ conditions: condition(SAFE_VALUE) })
144
+ ))
145
+ record('control: automation_triggers accepts normal event conditions', triggerControl.accepted, triggerControl.detail)
146
+
147
+ // NOTE: this is the case that caught the half fix — the orValidator's permissive arm accepted it.
148
+ const triggerPayload = await attempt(() => sdk.api.automation_triggers.createOne(
149
+ trigger({ conditions: condition(JS_PAYLOAD) })
150
+ ))
151
+ record('5a: automation_triggers rejects $JS( in event.conditions', !triggerPayload.accepted, triggerPayload.detail)
152
+
153
+ const byFormIdPayload = await attempt(() => sdk.api.automation_triggers.createOne(
154
+ trigger({}, { conditionsByFormId: { [form.id]: condition(JS_PAYLOAD) } })
155
+ ))
156
+ record('5b: automation_triggers rejects $JS( in event.info.conditionsByFormId', !byFormIdPayload.accepted, byFormIdPayload.detail)
157
+ } finally {
158
+ // Teardown BEFORE anything asserts — assert calls process.exit, which skips pending finallys and
159
+ // would strand the provisioned tenant permanently.
160
+ if (tenant) { try { await tenant.teardown() } catch (e) { console.error('teardown failed', e) } }
161
+ }
162
+
163
+ return checks
164
+ }
165
+
166
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
167
+ export const form_logic_js_execution_tests = async () => {
168
+ assert_checks('$JS in stored conditional logic', await form_logic_js_execution_checks())
169
+ }
170
+
171
+ // Allow running this test file independently
172
+ if (require.main === module) {
173
+ console.log(`🌐 Using API URL: ${host}`)
174
+ form_logic_js_execution_tests()
175
+ .then(() => {
176
+ console.log("✅ form logic JS execution test suite completed successfully")
177
+ process.exit(0)
178
+ })
179
+ .catch((error) => {
180
+ console.error("❌ form logic JS execution test suite failed:", error)
181
+ process.exit(1)
182
+ })
183
+ }
@@ -10,8 +10,7 @@ import { setup_tests } from "../../setup"
10
10
  const host = process.env.API_URL || 'http://localhost:8080' as const
11
11
 
12
12
  /**
13
- * Regression test for F-0145
14
- * (security-audit/findings/F-0145-globalunique-conflict-leaks-cross-tenant-record.md).
13
+ * Regression test: a globalUnique conflict response must not echo the matched record.
15
14
  *
16
15
  * The create endpoint generator's unconditional globalUnique-enforcement block
17
16
  * (routing.ts, "globalUnique should be enforced even if _overrideUnique is true")
@@ -24,7 +23,7 @@ const host = process.env.API_URL || 'http://localhost:8080' as const
24
23
  * 409 body's existingRecord exposes nothing beyond _id + businessId.
25
24
  */
26
25
  export const globalunique_leak_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
27
- log_header("F-0145: globalUnique conflict response redaction")
26
+ log_header("globalUnique conflict response redaction")
28
27
 
29
28
  let userId: string | undefined
30
29
  try {
@@ -33,7 +32,7 @@ export const globalunique_leak_tests = async ({ sdk } : { sdk: Session, sdkNonAd
33
32
  userId = user.id
34
33
 
35
34
  await async_test(
36
- 'F-0145: globalUnique 409 existingRecord exposes only _id + businessId (no email/hashedPass)',
35
+ 'globalUnique 409 existingRecord exposes only _id + businessId (no email/hashedPass)',
37
36
  () => sdk.api.users.createOne({ email, _overrideUnique: true } as any),
38
37
  {
39
38
  shouldError: true,
@@ -68,11 +67,11 @@ if (require.main === module) {
68
67
 
69
68
  runTests()
70
69
  .then(() => {
71
- console.log("✅ F-0145 globalUnique leak test suite completed successfully")
70
+ console.log("✅ globalUnique leak test suite completed successfully")
72
71
  process.exit(0)
73
72
  })
74
73
  .catch((error) => {
75
- console.error("❌ F-0145 globalUnique leak test suite failed:", error)
74
+ console.error("❌ globalUnique leak test suite failed:", error)
76
75
  process.exit(1)
77
76
  })
78
77
  }
@@ -27,7 +27,7 @@ const post = async (path: string, body: any, headers: Record<string, string> = {
27
27
  }
28
28
 
29
29
  /**
30
- * Regression test for F-0008 (security-audit/findings/F-0008-handle-incoming-communication-cross-tenant-enduser-lookup.md).
30
+ * Regression test: `handle_incoming_communication` tenant scoping.
31
31
  *
32
32
  * `journeys.handle_incoming_communication` previously used `buildAllQueries({ unrestricted: true, organizationIds: [] }).endusers.findById(enduserId)`,
33
33
  * permitting cross-tenant lookup of any enduser by id. The handler then called `handleIncomingCommunication(...)`
@@ -45,7 +45,7 @@ const post = async (path: string, body: any, headers: Record<string, string> = {
45
45
  * enduser — the post-fix code returns 404 before any side effects fire, and the assertion confirms that.
46
46
  */
47
47
  export const handle_incoming_communication_cross_tenant_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
48
- log_header("F-0008: handle_incoming_communication cross-tenant rejection")
48
+ log_header("handle_incoming_communication cross-tenant rejection")
49
49
 
50
50
  // ====================================================================
51
51
  // Assertion 1: nonexistent enduserId returns 404 (baseline; regression
@@ -58,19 +58,19 @@ export const handle_incoming_communication_cross_tenant_tests = async ({ sdk, sd
58
58
  { Authorization: `Bearer ${sdk.authToken}` },
59
59
  )
60
60
  await async_test(
61
- "F-0008: handle_incoming_communication with nonexistent enduserId returns 404",
61
+ "handle_incoming_communication with nonexistent enduserId returns 404",
62
62
  async () => ({ status: nonexistentRes.status }),
63
63
  { onResult: r => r.status === 404 },
64
64
  )
65
65
 
66
66
  // ====================================================================
67
- // Assertion 2: cross-tenant enduserId returns 404 (the actual F-0008 fix).
67
+ // Assertion 2: cross-tenant enduserId returns 404 (the actual fix).
68
68
  // Env-gated; skipped when cross-org infra isn't configured.
69
69
  // Safe to run post-fix because the tenant-scoped DB returns null for
70
70
  // cross-tenant lookups — no handleIncomingCommunication side effect fires.
71
71
  // ====================================================================
72
72
  if (!(CROSS_ORG_API_KEY && CROSS_ORG_TARGET_BUSINESS_ID)) {
73
- console.log(" [F-0008] Skipping cross-tenant rejection assertion — CROSS_ORG_* env vars not set")
73
+ console.log(" Skipping cross-tenant rejection assertion — CROSS_ORG_* env vars not set")
74
74
  return
75
75
  }
76
76
 
@@ -96,7 +96,7 @@ export const handle_incoming_communication_cross_tenant_tests = async ({ sdk, sd
96
96
  )
97
97
 
98
98
  await async_test(
99
- "F-0008: handle_incoming_communication with cross-tenant enduserId returns 404 (no side effect)",
99
+ "handle_incoming_communication with cross-tenant enduserId returns 404 (no side effect)",
100
100
  async () => ({
101
101
  status: crossRes.status,
102
102
  message: crossRes.data?.message ?? null,
@@ -120,11 +120,11 @@ if (require.main === module) {
120
120
 
121
121
  runTests()
122
122
  .then(() => {
123
- console.log("✅ F-0008 handle_incoming_communication cross-tenant test suite completed successfully")
123
+ console.log("✅ handle_incoming_communication cross-tenant test suite completed successfully")
124
124
  process.exit(0)
125
125
  })
126
126
  .catch((error) => {
127
- console.error("❌ F-0008 handle_incoming_communication cross-tenant test suite failed:", error)
127
+ console.error("❌ handle_incoming_communication cross-tenant test suite failed:", error)
128
128
  process.exit(1)
129
129
  })
130
130
  }
@@ -27,7 +27,7 @@ const post = async (path: string, body: any, headers: Record<string, string> = {
27
27
  }
28
28
 
29
29
  /**
30
- * Regression test for F-0007 (security-audit/findings/F-0007-invite-user-cross-tenant-email-enumeration.md).
30
+ * Regression test: `invite_user` cross-tenant enumeration.
31
31
  *
32
32
  * `users.invite_user` previously used `buildAllQueries({ unrestricted: true, organizationIds: [] }).users.findOne({ email })`
33
33
  * to enforce platform-wide email uniqueness and threw the distinctive `"A user with this email already exists"`
@@ -50,7 +50,7 @@ const post = async (path: string, body: any, headers: Record<string, string> = {
50
50
  * env vars are not set, mirroring cross_org_api_key.test.ts convention.
51
51
  */
52
52
  export const invite_user_enumeration_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
53
- log_header("F-0007: users.invite_user cross-tenant enumeration regression")
53
+ log_header("users.invite_user cross-tenant enumeration regression")
54
54
 
55
55
  // Reset state so prior tests' rate-limit accounting doesn't leak in.
56
56
  await sdk.reset_db()
@@ -85,7 +85,7 @@ export const invite_user_enumeration_tests = async ({ sdk, sdkNonAdmin } : { sdk
85
85
  }
86
86
 
87
87
  await async_test(
88
- "F-0007: invite_user rate-limits within ~12 rapid requests (defense-in-depth; no invites sent)",
88
+ "invite_user rate-limits within ~12 rapid requests (defense-in-depth; no invites sent)",
89
89
  async () => ({ rateLimitedAt }),
90
90
  { onResult: r => r.rateLimitedAt >= 0 && r.rateLimitedAt <= 12 },
91
91
  )
@@ -111,7 +111,7 @@ export const invite_user_enumeration_tests = async ({ sdk, sdkNonAdmin } : { sdk
111
111
  )
112
112
 
113
113
  await async_test(
114
- "F-0007: invite_user same-tenant duplicate returns 400 'already exists' (unchanged)",
114
+ "invite_user same-tenant duplicate returns 400 'already exists' (unchanged)",
115
115
  async () => ({
116
116
  status: sameTenantRes.status,
117
117
  message: sameTenantRes.data?.message ?? '',
@@ -124,7 +124,7 @@ export const invite_user_enumeration_tests = async ({ sdk, sdkNonAdmin } : { sdk
124
124
  // Env-gated; skipped when cross-org infra not configured.
125
125
  // ====================================================================
126
126
  if (!(CROSS_ORG_API_KEY && CROSS_ORG_TARGET_BUSINESS_ID)) {
127
- console.log(" [F-0007] Skipping cross-tenant silent no-op assertion — CROSS_ORG_* env vars not set")
127
+ console.log(" Skipping cross-tenant silent no-op assertion — CROSS_ORG_* env vars not set")
128
128
  return
129
129
  }
130
130
 
@@ -160,7 +160,7 @@ export const invite_user_enumeration_tests = async ({ sdk, sdkNonAdmin } : { sdk
160
160
  )
161
161
 
162
162
  await async_test(
163
- "F-0007: cross-tenant invite of existing email must NOT return 'already exists' (no enumeration)",
163
+ "cross-tenant invite of existing email must NOT return 'already exists' (no enumeration)",
164
164
  async () => ({
165
165
  status: crossRes.status,
166
166
  message: crossRes.data?.message ?? '',
@@ -188,11 +188,11 @@ if (require.main === module) {
188
188
 
189
189
  runTests()
190
190
  .then(() => {
191
- console.log("✅ F-0007 invite_user enumeration test suite completed successfully")
191
+ console.log("✅ invite_user enumeration test suite completed successfully")
192
192
  process.exit(0)
193
193
  })
194
194
  .catch((error) => {
195
- console.error("❌ F-0007 invite_user enumeration test suite failed:", error)
195
+ console.error("❌ invite_user enumeration test suite failed:", error)
196
196
  process.exit(1)
197
197
  })
198
198
  }