@tellescope/sdk 1.256.14 → 1.256.15
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.
- package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js +1 -1
- package/lib/cjs/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js +388 -0
- package/lib/cjs/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js +3 -3
- package/lib/cjs/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +1 -1
- package/lib/cjs/tests/api_tests/organization_signup.test.js +1 -1
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +103 -162
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +249 -0
- package/lib/cjs/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +281 -0
- package/lib/cjs/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js +390 -0
- package/lib/cjs/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js +1003 -0
- package/lib/cjs/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js +323 -0
- package/lib/cjs/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js +335 -0
- package/lib/cjs/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +237 -0
- package/lib/cjs/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js +349 -0
- package/lib/cjs/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +314 -0
- package/lib/cjs/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js +362 -0
- package/lib/cjs/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js +366 -0
- package/lib/cjs/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js +248 -0
- package/lib/cjs/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js +138 -0
- package/lib/cjs/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +201 -0
- package/lib/cjs/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js +278 -0
- package/lib/cjs/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js +155 -0
- package/lib/cjs/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js +88 -0
- package/lib/cjs/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +230 -0
- package/lib/cjs/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +242 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js +148 -0
- package/lib/cjs/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js +388 -0
- package/lib/cjs/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js +222 -0
- package/lib/cjs/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +354 -0
- package/lib/cjs/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js +442 -0
- package/lib/cjs/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js +156 -0
- package/lib/cjs/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/webhook_error_handling.test.js +2 -2
- package/lib/cjs/tests/api_tests/webhook_error_handling.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +58 -46
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +1 -1
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +1 -1
- package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/esm/enduser.d.ts +0 -1
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/sdk.d.ts +2 -3
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/session.d.ts +0 -1
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js +1 -1
- package/lib/esm/tests/api_tests/auto_merge_form_submission.test.js.map +1 -1
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts +5 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js +383 -0
- package/lib/esm/tests/api_tests/enduser_observations_auto_review.test.js.map +1 -0
- package/lib/esm/tests/api_tests/integrations_redacted.test.js +3 -3
- package/lib/esm/tests/api_tests/integrations_redacted.test.js.map +1 -1
- package/lib/esm/tests/api_tests/organization_signup.test.d.ts +1 -1
- package/lib/esm/tests/api_tests/organization_signup.test.js +1 -1
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +104 -163
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts +33 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js +244 -0
- package/lib/esm/tests/api_tests/security/F-0200-form-logic-js-execution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts +42 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js +276 -0
- package/lib/esm/tests/api_tests/security/add-to-journey-foreign-enduserid.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts +38 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js +386 -0
- package/lib/esm/tests/api_tests/security/ai-conversations-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js +996 -0
- package/lib/esm/tests/api_tests/security/allowed-paths-confinement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts +42 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js +316 -0
- package/lib/esm/tests/api_tests/security/automated-actions-process-gate.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js +331 -0
- package/lib/esm/tests/api_tests/security/booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts +32 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js +233 -0
- package/lib/esm/tests/api_tests/security/cascade-role-rename-cross-tenant.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts +28 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js +345 -0
- package/lib/esm/tests/api_tests/security/data-sync-redaction-bypass.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts +30 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js +310 -0
- package/lib/esm/tests/api_tests/security/develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts +21 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js +358 -0
- package/lib/esm/tests/api_tests/security/enduser-cross-create.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts +21 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js +362 -0
- package/lib/esm/tests/api_tests/security/enduser-write-restrictions.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts +32 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js +243 -0
- package/lib/esm/tests/api_tests/security/form-logic-js-execution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts +19 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js +134 -0
- package/lib/esm/tests/api_tests/security/globalunique-conflict-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts +24 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js +194 -0
- package/lib/esm/tests/api_tests/security/handle-incoming-communication-cross-tenant.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js +271 -0
- package/lib/esm/tests/api_tests/security/invite-user-enumeration.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts +17 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js +151 -0
- package/lib/esm/tests/api_tests/security/load-inbox-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.js +84 -0
- package/lib/esm/tests/api_tests/security/prototype-pollution.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts +34 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js +223 -0
- package/lib/esm/tests/api_tests/security/proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js +238 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js +144 -0
- package/lib/esm/tests/api_tests/security/sanitize-user-html.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts +45 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js +381 -0
- package/lib/esm/tests/api_tests/security/schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts +38 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js +218 -0
- package/lib/esm/tests/api_tests/security/self-admin-role-assignment.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js +350 -0
- package/lib/esm/tests/api_tests/security/startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts +28 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js +435 -0
- package/lib/esm/tests/api_tests/security/webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts +19 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.js +149 -0
- package/lib/esm/tests/api_tests/security/webhook-timeout.test.js.map +1 -0
- package/lib/esm/tests/api_tests/webhook_error_handling.test.js +2 -2
- package/lib/esm/tests/api_tests/webhook_error_handling.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +36 -24
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +1 -1
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/auto_merge_form_submission.test.ts +1 -1
- package/src/tests/api_tests/enduser_observations_auto_review.test.ts +358 -0
- package/src/tests/api_tests/integrations_redacted.test.ts +1 -1
- package/src/tests/api_tests/organization_signup.test.ts +2 -2
- package/src/tests/api_tests/purchase_made_trigger.test.ts +29 -41
- package/src/tests/api_tests/security/{F-0196-add-to-journey-foreign-enduserid.test.ts → add-to-journey-foreign-enduserid.test.ts} +6 -7
- package/src/tests/api_tests/security/{F-0005-ai-conversations-rbac.test.ts → ai-conversations-rbac.test.ts} +14 -14
- package/src/tests/api_tests/security/{F-0166-allowed-paths-wildcard-bypass.test.ts → allowed-paths-confinement.test.ts} +11 -11
- package/src/tests/api_tests/security/{F-0192-automated-actions-process-gate.test.ts → automated-actions-process-gate.test.ts} +6 -7
- package/src/tests/api_tests/security/{F-0179-booking-info-idor-token-mint.test.ts → booking-info-idor-token-mint.test.ts} +5 -6
- package/src/tests/api_tests/security/{F-0053-cascade-role-rename-cross-tenant.test.ts → cascade-role-rename-cross-tenant.test.ts} +10 -10
- package/src/tests/api_tests/security/{F-0001-data-sync-redaction-bypass.test.ts → data-sync-redaction-bypass.test.ts} +9 -9
- package/src/tests/api_tests/security/{F-0006-develop-health-benefit-verification-rbac.test.ts → develop-health-benefit-verification-rbac.test.ts} +7 -8
- package/src/tests/api_tests/security/{F-0116-F-0119-enduser-cross-create.test.ts → enduser-cross-create.test.ts} +25 -25
- package/src/tests/api_tests/security/{F-0106-F-0110-enduser-write-restrictions.test.ts → enduser-write-restrictions.test.ts} +12 -14
- package/src/tests/api_tests/security/form-logic-js-execution.test.ts +183 -0
- package/src/tests/api_tests/security/{F-0145-globalunique-leak.test.ts → globalunique-conflict-redaction.test.ts} +5 -6
- package/src/tests/api_tests/security/{F-0008-handle-incoming-communication-cross-tenant.test.ts → handle-incoming-communication-cross-tenant.test.ts} +8 -8
- package/src/tests/api_tests/security/{F-0007-invite-user-enumeration.test.ts → invite-user-enumeration.test.ts} +8 -8
- package/src/tests/api_tests/security/{F-0151-load-inbox-redaction.test.ts → load-inbox-redaction.test.ts} +5 -6
- package/src/tests/api_tests/security/{F-0016-prototype-pollution.test.ts → prototype-pollution.test.ts} +4 -4
- package/src/tests/api_tests/security/{F-0025-proxy-image-unauth-ssrf.test.ts → proxy-image-unauth-ssrf.test.ts} +11 -12
- package/src/tests/api_tests/security/{F-0013-sanitize-user-html-with-iframes.test.ts → sanitize-user-html-with-iframes.test.ts} +5 -5
- package/src/tests/api_tests/security/{F-0013-sanitize-user-html.test.ts → sanitize-user-html.test.ts} +4 -4
- package/src/tests/api_tests/security/{F-0191-schema-adminonly-enforcement.test.ts → schema-adminonly-enforcement.test.ts} +6 -7
- package/src/tests/api_tests/security/{F-0076-self-admin-role-assignment.test.ts → self-admin-role-assignment.test.ts} +13 -13
- package/src/tests/api_tests/security/{F-0185-startlinktoken-enduser-staff-escalation.test.ts → startlinktoken-enduser-staff-escalation.test.ts} +6 -6
- package/src/tests/api_tests/security/{F-0015-webhook-outbound-ssrf.test.ts → webhook-outbound-ssrf.test.ts} +13 -14
- package/src/tests/api_tests/security/{F-0155-webhook-timeout.test.ts → webhook-timeout.test.ts} +6 -7
- package/src/tests/api_tests/webhook_error_handling.test.ts +1 -1
- package/src/tests/tests.ts +41 -24
- package/src/tests/unit_tests/openapi_spec_freshness.test.ts +1 -1
- package/test_generated.pdf +0 -0
- package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +0 -301
|
@@ -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
|
|
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("
|
|
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
|
-
'
|
|
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("✅
|
|
70
|
+
console.log("✅ globalUnique leak test suite completed successfully")
|
|
72
71
|
process.exit(0)
|
|
73
72
|
})
|
|
74
73
|
.catch((error) => {
|
|
75
|
-
console.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
|
|
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("
|
|
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
|
-
"
|
|
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
|
|
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("
|
|
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
|
-
"
|
|
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("✅
|
|
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("❌
|
|
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
|
|
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("
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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("
|
|
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
|
-
"
|
|
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("✅
|
|
191
|
+
console.log("✅ invite_user enumeration test suite completed successfully")
|
|
192
192
|
process.exit(0)
|
|
193
193
|
})
|
|
194
194
|
.catch((error) => {
|
|
195
|
-
console.error("❌
|
|
195
|
+
console.error("❌ invite_user enumeration test suite failed:", error)
|
|
196
196
|
process.exit(1)
|
|
197
197
|
})
|
|
198
198
|
}
|
|
@@ -12,8 +12,7 @@ const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
|
12
12
|
const ENDUSER_PASSWORD = 'F0151TestPassword!123'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Regression test
|
|
16
|
-
* (security-audit/findings/F-0151-load-inbox-data-endusers-unredacted.md).
|
|
15
|
+
* Regression test: `load_inbox_data` must apply enduser field redactions.
|
|
17
16
|
*
|
|
18
17
|
* load_inbox_data serialized $lookup'ed endusers via convert_id_name only (NOT applyRedactions),
|
|
19
18
|
* so endusers.hashedPassword (redactions:['all']) leaked to staff sessions — while the sibling
|
|
@@ -24,7 +23,7 @@ const ENDUSER_PASSWORD = 'F0151TestPassword!123'
|
|
|
24
23
|
* assert the returned enduser entry has NO hashedPassword.
|
|
25
24
|
*/
|
|
26
25
|
export const load_inbox_redaction_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
27
|
-
log_header("
|
|
26
|
+
log_header("load_inbox_data enduser redaction")
|
|
28
27
|
|
|
29
28
|
let enduserId: string | undefined
|
|
30
29
|
let emailId: string | undefined
|
|
@@ -42,7 +41,7 @@ export const load_inbox_redaction_tests = async ({ sdk } : { sdk: Session, sdkNo
|
|
|
42
41
|
emailId = seeded.id
|
|
43
42
|
|
|
44
43
|
await async_test(
|
|
45
|
-
'
|
|
44
|
+
'load_inbox_data does not leak enduser.hashedPassword to staff',
|
|
46
45
|
() => sdk.api.endusers.load_inbox_data({ enduserIds: [enduser.id] } as any),
|
|
47
46
|
{
|
|
48
47
|
onResult: (r: any) => {
|
|
@@ -71,11 +70,11 @@ if (require.main === module) {
|
|
|
71
70
|
|
|
72
71
|
runTests()
|
|
73
72
|
.then(() => {
|
|
74
|
-
console.log("✅
|
|
73
|
+
console.log("✅ load_inbox_data redaction test suite completed successfully")
|
|
75
74
|
process.exit(0)
|
|
76
75
|
})
|
|
77
76
|
.catch((error) => {
|
|
78
|
-
console.error("❌
|
|
77
|
+
console.error("❌ load_inbox_data redaction test suite failed:", error)
|
|
79
78
|
process.exit(1)
|
|
80
79
|
})
|
|
81
80
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { add_value_for_dotted_key } from "@tellescope/utilities"
|
|
2
2
|
|
|
3
|
-
// Regression test
|
|
3
|
+
// Regression test: prototype pollution via dotted-key assignment.
|
|
4
4
|
// add_value_for_dotted_key must NOT write through __proto__/constructor/prototype path segments
|
|
5
5
|
// (which would pollute Object.prototype process-wide), while still performing legitimate dotted assignment.
|
|
6
6
|
//
|
|
7
7
|
// Pure-function test — no Session needed. Runs in the main suite and standalone:
|
|
8
|
-
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/
|
|
8
|
+
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/prototype-pollution.test.js
|
|
9
9
|
|
|
10
10
|
const fail = (msg: string) => { throw new Error(msg) }
|
|
11
11
|
|
|
12
12
|
export const prototype_pollution_tests = async () => {
|
|
13
|
-
console.log("Running
|
|
13
|
+
console.log("Running prototype-pollution regression tests")
|
|
14
14
|
|
|
15
15
|
// 1. __proto__ path must not pollute Object.prototype
|
|
16
16
|
add_value_for_dotted_key({ insurance: {} } as any, 'insurance.__proto__.__pp_a__', 'polluted')
|
|
@@ -40,7 +40,7 @@ export const prototype_pollution_tests = async () => {
|
|
|
40
40
|
add_value_for_dotted_key(flat, 'name', 'ok')
|
|
41
41
|
if (flat.name !== 'ok') fail('single-key assignment broke')
|
|
42
42
|
|
|
43
|
-
console.log("✅
|
|
43
|
+
console.log("✅ prototype-pollution regression tests passed")
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (require.main === module) {
|
|
@@ -22,15 +22,14 @@ const assert_local_api = () => {
|
|
|
22
22
|
const hostname = new URL(host).hostname
|
|
23
23
|
if (!LOCAL_HOSTNAMES.includes(hostname)) {
|
|
24
24
|
throw new Error(
|
|
25
|
-
`
|
|
25
|
+
`SSRF tests must run against a local API (got ${host}). `
|
|
26
26
|
+ `127.0.0.1 would resolve to the API server rather than this machine, so the tests would be meaningless.`
|
|
27
27
|
)
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Regression tests
|
|
33
|
-
* (security-audit/findings/F-0025-proxy-image-unauth-ssrf.md).
|
|
32
|
+
* Regression tests: `/v1/proxy-image` must refuse internal and non-image destinations.
|
|
34
33
|
*
|
|
35
34
|
* GET /v1/proxy-image accepted an arbitrary ?url= and streamed the upstream response body straight
|
|
36
35
|
* back with Access-Control-Allow-Origin: *, with no authentication, no destination validation and no
|
|
@@ -58,7 +57,7 @@ const assert_local_api = () => {
|
|
|
58
57
|
* the vulnerability proof.
|
|
59
58
|
*/
|
|
60
59
|
export const proxy_image_ssrf_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
61
|
-
log_header("
|
|
60
|
+
log_header("proxy-image unauthenticated SSRF")
|
|
62
61
|
assert_local_api()
|
|
63
62
|
|
|
64
63
|
const SENTINEL = `f0025-sentinel-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
@@ -95,7 +94,7 @@ export const proxy_image_ssrf_tests = async ({ sdk } : { sdk: Session, sdkNonAdm
|
|
|
95
94
|
|
|
96
95
|
// T1 — the core SSRF: an unauthenticated caller must not be able to read an internal resource.
|
|
97
96
|
await async_test(
|
|
98
|
-
'
|
|
97
|
+
'proxy-image does not stream an internal non-image resource to an unauthenticated caller',
|
|
99
98
|
call_unauthenticated,
|
|
100
99
|
{
|
|
101
100
|
// Positive control: a numeric status proves we actually reached our API rather than failing to
|
|
@@ -111,7 +110,7 @@ export const proxy_image_ssrf_tests = async ({ sdk } : { sdk: Session, sdkNonAdm
|
|
|
111
110
|
// T2 — the unauthenticated surface itself: the request must be refused outright, and the
|
|
112
111
|
// destination must never be contacted (no SSRF-as-a-side-effect even when the body is withheld).
|
|
113
112
|
await async_test(
|
|
114
|
-
'
|
|
113
|
+
'proxy-image refuses an unauthenticated request without contacting the destination',
|
|
115
114
|
call_unauthenticated,
|
|
116
115
|
{ onResult: (r: any) => r.status >= 400 && r.status < 500 && r.sentinelHits === 0 },
|
|
117
116
|
)
|
|
@@ -129,26 +128,26 @@ export const proxy_image_ssrf_tests = async ({ sdk } : { sdk: Session, sdkNonAdm
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
await async_test(
|
|
132
|
-
'
|
|
131
|
+
'proxy-image refuses a private destination even for an authenticated staff session',
|
|
133
132
|
() => call_authenticated(sentinelURL),
|
|
134
133
|
{ onResult: (r: any) => r.status === 400 && r.sentinelHits === 0 },
|
|
135
134
|
)
|
|
136
135
|
|
|
137
136
|
await async_test(
|
|
138
|
-
'
|
|
137
|
+
'proxy-image refuses cloud metadata for an authenticated staff session',
|
|
139
138
|
() => call_authenticated('https://169.254.169.254/latest/meta-data/'),
|
|
140
139
|
{ onResult: (r: any) => r.status === 400 },
|
|
141
140
|
)
|
|
142
141
|
|
|
143
142
|
// Scheme and port are pinned: only https on 443, which is all the PDF export ever needs.
|
|
144
143
|
await async_test(
|
|
145
|
-
'
|
|
144
|
+
'proxy-image refuses a plain http destination',
|
|
146
145
|
() => call_authenticated('http://example.com/logo.png'),
|
|
147
146
|
{ onResult: (r: any) => r.status === 400 },
|
|
148
147
|
)
|
|
149
148
|
|
|
150
149
|
await async_test(
|
|
151
|
-
'
|
|
150
|
+
'proxy-image refuses a non-443 port',
|
|
152
151
|
() => call_authenticated('https://example.com:8443/logo.png'),
|
|
153
152
|
{ onResult: (r: any) => r.status === 400 },
|
|
154
153
|
)
|
|
@@ -169,11 +168,11 @@ if (require.main === module) {
|
|
|
169
168
|
|
|
170
169
|
runTests()
|
|
171
170
|
.then(() => {
|
|
172
|
-
console.log("✅
|
|
171
|
+
console.log("✅ proxy-image SSRF test suite completed successfully")
|
|
173
172
|
process.exit(0)
|
|
174
173
|
})
|
|
175
174
|
.catch((error) => {
|
|
176
|
-
console.error("❌
|
|
175
|
+
console.error("❌ proxy-image SSRF test suite failed:", error)
|
|
177
176
|
process.exit(1)
|
|
178
177
|
})
|
|
179
178
|
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { DEFAULT_IFRAME_SANDBOX, replace_form_field_template_values, sanitize_user_html, sanitize_user_html_with_iframes } from "@tellescope/utilities"
|
|
2
2
|
|
|
3
|
-
// Companion to
|
|
3
|
+
// Companion to sanitize-user-html.test.ts (pattern 06 — XSS via dangerouslySetInnerHTML).
|
|
4
4
|
//
|
|
5
5
|
// sanitize_user_html_with_iframes is the variant used by the live form's field description, so
|
|
6
6
|
// admins can embed video / scheduling widgets. It permits <iframe> and NOTHING else beyond what
|
|
7
7
|
// sanitize_user_html allows. This asserts:
|
|
8
|
-
// 1. permitting iframe did not reopen any of the
|
|
8
|
+
// 1. permitting iframe did not reopen any of the base vectors (the full payload list is re-run)
|
|
9
9
|
// 2. only absolute https iframes survive — relative, protocol-relative, http, javascript:, data:
|
|
10
10
|
// and srcdoc frames are dropped, along with their fallback content
|
|
11
11
|
// 3. the restrictive sandbox is forced, but an author-supplied sandbox is honored
|
|
12
12
|
// 4. sanitize_user_html itself still strips iframes (the boundary between the two functions)
|
|
13
13
|
//
|
|
14
14
|
// Pure-function test — no Session needed. Runs as part of the main suite and standalone:
|
|
15
|
-
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/
|
|
15
|
+
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/sanitize-user-html-with-iframes.test.js
|
|
16
16
|
|
|
17
17
|
const fail = (msg: string) => { throw new Error(msg) }
|
|
18
18
|
|
|
19
|
-
// Same as has_no_executable_vector in the
|
|
19
|
+
// Same as has_no_executable_vector in the base sanitizer test, minus the '<iframe' clause (iframes are
|
|
20
20
|
// expected in this function's output). Everything else must still be neutralized.
|
|
21
21
|
const has_no_executable_vector_allowing_iframe = (out: string) => {
|
|
22
22
|
const o = out.toLowerCase()
|
|
@@ -49,7 +49,7 @@ export const sanitize_user_html_with_iframes_tests = async () => {
|
|
|
49
49
|
console.log("Running sanitize_user_html_with_iframes XSS regression tests")
|
|
50
50
|
|
|
51
51
|
// ---------------------------------------------------------------------------------------------
|
|
52
|
-
// 1. Re-run the full
|
|
52
|
+
// 1. Re-run the full base payload set — permitting iframe must not be a general regression
|
|
53
53
|
// ---------------------------------------------------------------------------------------------
|
|
54
54
|
const xssPayloads: [string, string][] = [
|
|
55
55
|
['img onerror', `<img src=x onerror="alert(document.domain)">`],
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { sanitize_user_html } from "@tellescope/utilities"
|
|
2
2
|
|
|
3
|
-
// Regression test
|
|
3
|
+
// Regression test: XSS via dangerouslySetInnerHTML.
|
|
4
4
|
// sanitize_user_html is the canonical render-time sanitizer that replaced remove_script_tags
|
|
5
5
|
// at every dangerouslySetInnerHTML sink. This asserts it neutralizes XSS vectors (incl. encoded /
|
|
6
6
|
// whitespace / mixed-case / iframe-srcdoc bypass variants) while preserving legitimate
|
|
7
7
|
// customization HTML (tables, headings, lists, links, images, inline styles).
|
|
8
8
|
//
|
|
9
9
|
// Pure-function test — no Session needed. Runs as part of the main suite and standalone:
|
|
10
|
-
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/
|
|
10
|
+
// ./build_cjs.sh && cd packages/public/sdk && node -r dotenv/config lib/cjs/tests/api_tests/security/sanitize-user-html.test.js
|
|
11
11
|
|
|
12
12
|
const fail = (msg: string) => { throw new Error(msg) }
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ const has_no_executable_vector = (out: string) => {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const sanitize_user_html_xss_tests = async () => {
|
|
34
|
-
console.log("Running
|
|
34
|
+
console.log("Running sanitize_user_html XSS regression tests")
|
|
35
35
|
|
|
36
36
|
const xssPayloads: [string, string][] = [
|
|
37
37
|
['img onerror', `<img src=x onerror="alert(document.domain)">`],
|
|
@@ -99,7 +99,7 @@ export const sanitize_user_html_xss_tests = async () => {
|
|
|
99
99
|
const mixed = sanitize_user_html(`<p>Hello <b>name</b></p><img src=x onerror="steal()">`)
|
|
100
100
|
if (!(mixed.includes('<b>name</b>') && !/\son[a-z]+\s*=/.test(mixed.toLowerCase()))) fail(`mixed content not handled: ${mixed}`)
|
|
101
101
|
|
|
102
|
-
console.log("✅
|
|
102
|
+
console.log("✅ sanitize_user_html XSS regression tests passed")
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (require.main === module) {
|
|
@@ -24,8 +24,7 @@ type Check = {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Regression test
|
|
28
|
-
* (security-audit/findings/F-0191-schema-adminonly-not-enforced.md).
|
|
27
|
+
* Regression test: `adminOnly` custom actions must be enforced at runtime, not merely declared.
|
|
29
28
|
*
|
|
30
29
|
* `adminOnly: true` on a CUSTOM action in schema.ts was documentation-only. The runtime gate
|
|
31
30
|
* (routing.ts:944) reads the flag off the options object built by getCustomEndpointFields, and
|
|
@@ -64,7 +63,7 @@ type Check = {
|
|
|
64
63
|
* GREEN (post-fix): all six return 401 "Admin access only"; the group-A controls are unchanged.
|
|
65
64
|
*/
|
|
66
65
|
export const schema_adminonly_enforcement_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
67
|
-
log_header("
|
|
66
|
+
log_header("schema-declared adminOnly must be enforced on custom actions")
|
|
68
67
|
|
|
69
68
|
const checks: Check[] = []
|
|
70
69
|
const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
|
|
@@ -225,7 +224,7 @@ export const schema_adminonly_enforcement_tests = async ({ sdk } : { sdk: Sessio
|
|
|
225
224
|
{
|
|
226
225
|
label: 'organizations.sync_note_to_canvas',
|
|
227
226
|
method: 'post', path: '/organizations/sync-note-to-canvas',
|
|
228
|
-
data: { enduserId: ABSENT_ID, note: '
|
|
227
|
+
data: { enduserId: ABSENT_ID, note: 'control' }, // 404 Enduser not found
|
|
229
228
|
},
|
|
230
229
|
{
|
|
231
230
|
label: 'background_errors.mark_read',
|
|
@@ -249,7 +248,7 @@ export const schema_adminonly_enforcement_tests = async ({ sdk } : { sdk: Sessio
|
|
|
249
248
|
}
|
|
250
249
|
|
|
251
250
|
/* ===== report: print full table first, then assert (harness exits on first failure) ===== */
|
|
252
|
-
console.log('\n
|
|
251
|
+
console.log('\n check summary:')
|
|
253
252
|
for (const c of checks) {
|
|
254
253
|
console.log(` ${c.pass ? 'PASS' : 'FAIL'} ${c.label} — ${c.detail}`)
|
|
255
254
|
}
|
|
@@ -278,11 +277,11 @@ if (require.main === module) {
|
|
|
278
277
|
|
|
279
278
|
runTests()
|
|
280
279
|
.then(() => {
|
|
281
|
-
console.log("✅
|
|
280
|
+
console.log("✅ schema adminOnly enforcement test suite completed successfully")
|
|
282
281
|
process.exit(0)
|
|
283
282
|
})
|
|
284
283
|
.catch((error) => {
|
|
285
|
-
console.error("❌
|
|
284
|
+
console.error("❌ schema adminOnly enforcement test suite failed:", error)
|
|
286
285
|
process.exit(1)
|
|
287
286
|
})
|
|
288
287
|
}
|