@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.
- package/lib/cjs/sdk.d.ts +2 -2
- 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/file-link-session-confinement.test.d.ts +5 -0
- package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/file-link-session-confinement.test.js +357 -0
- package/lib/cjs/tests/api_tests/security/file-link-session-confinement.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 +60 -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 +4 -5
- 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/file-link-session-confinement.test.d.ts +5 -0
- package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/file-link-session-confinement.test.js +349 -0
- package/lib/esm/tests/api_tests/security/file-link-session-confinement.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 +38 -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/file-link-session-confinement.test.ts +291 -0
- 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 +43 -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
|
@@ -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
|
}
|
|
@@ -11,10 +11,10 @@ import { setup_tests } from "../../setup"
|
|
|
11
11
|
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Self-role privilege-escalation guard (relates to
|
|
14
|
+
* Self-role privilege-escalation guard (relates to a prior internal review, which was
|
|
15
15
|
* investigated and closed as a FALSE POSITIVE — see that file for the full code trace).
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* An earlier review hypothesized that a non-admin staff user could `PATCH /v1/users/{their-own-id}` with
|
|
18
18
|
* `{ roles: ['Admin'] }` and self-promote to Admin, because the FIRST `users` relationship
|
|
19
19
|
* constraint ("Only admin users can set the admin role",
|
|
20
20
|
* [schema.ts:3446](packages/public/schema/src/schema.ts#L3446)) has a self-exception
|
|
@@ -43,7 +43,7 @@ const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
|
43
43
|
* path role-writable by non-admins would flip the step-3/step-4 assertions to red.
|
|
44
44
|
*/
|
|
45
45
|
export const self_admin_role_assignment_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
46
|
-
log_header("
|
|
46
|
+
log_header("self-admin role assignment privilege-escalation regression")
|
|
47
47
|
|
|
48
48
|
const stamp = Date.now()
|
|
49
49
|
const NON_ADMIN_ROLE = 'Provider'
|
|
@@ -84,7 +84,7 @@ export const self_admin_role_assignment_tests = async ({ sdk } : { sdk: Session,
|
|
|
84
84
|
assert(
|
|
85
85
|
JSON.stringify(attackerBefore.roles) === JSON.stringify([NON_ADMIN_ROLE]),
|
|
86
86
|
`Setup failed: expected attacker to hold [${NON_ADMIN_ROLE}], got ${JSON.stringify(attackerBefore.roles)}`,
|
|
87
|
-
'
|
|
87
|
+
'setup: attacker holds a non-admin role',
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
// 2. Authenticate AS the attacker (no password needed — admin mints an auth token).
|
|
@@ -96,25 +96,25 @@ export const self_admin_role_assignment_tests = async ({ sdk } : { sdk: Session,
|
|
|
96
96
|
assert(
|
|
97
97
|
sdkAttacker.userInfo.id === attackerId && !(sdkAttacker.userInfo.roles ?? []).includes('Admin'),
|
|
98
98
|
`Setup failed: attacker session is not the expected non-admin user`,
|
|
99
|
-
'
|
|
99
|
+
'setup: authenticated as the non-admin attacker',
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
// 3. SECURITY ASSERTIONS — every self-role mutation by the non-admin must be blocked.
|
|
103
103
|
await expect_blocked(
|
|
104
104
|
() => sdkAttacker.api.users.updateOne(attackerId!, { roles: ['Admin'] }, { replaceObjectFields: true }),
|
|
105
|
-
'
|
|
105
|
+
'non-admin self-update to [Admin] is blocked',
|
|
106
106
|
)
|
|
107
107
|
await expect_blocked(
|
|
108
108
|
() => sdkAttacker.api.users.updateOne(attackerId!, { roles: [NON_ADMIN_ROLE, 'Admin'] }, { replaceObjectFields: true }),
|
|
109
|
-
'
|
|
109
|
+
'non-admin self-update to [Provider, Admin] is blocked',
|
|
110
110
|
)
|
|
111
111
|
await expect_blocked(
|
|
112
112
|
() => sdkAttacker.api.users.updateOne(attackerId!, { roles: [`Arbitrary_${stamp}`] }, { replaceObjectFields: true }),
|
|
113
|
-
'
|
|
113
|
+
'non-admin self-update to an arbitrary role is blocked',
|
|
114
114
|
)
|
|
115
115
|
await expect_blocked(
|
|
116
116
|
() => sdkAttacker.api.users.updateOne(attackerId!, { roles: [] }, { replaceObjectFields: true }),
|
|
117
|
-
'
|
|
117
|
+
'non-admin self-update to [] (would grant defaults) is blocked',
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
// 4. STATE ASSERTION — nothing slipped through; roles are still the original non-admin role.
|
|
@@ -123,7 +123,7 @@ export const self_admin_role_assignment_tests = async ({ sdk } : { sdk: Session,
|
|
|
123
123
|
JSON.stringify(attackerAfter.roles) === JSON.stringify([NON_ADMIN_ROLE]),
|
|
124
124
|
`ESCALATION LEAK: attacker roles changed to ${JSON.stringify(attackerAfter.roles)} `
|
|
125
125
|
+ `after self-update attempts. Expected [${NON_ADMIN_ROLE}].`,
|
|
126
|
-
'
|
|
126
|
+
'attacker roles unchanged after all self-escalation attempts',
|
|
127
127
|
)
|
|
128
128
|
|
|
129
129
|
// 5. POSITIVE CONTROL — an Admin CAN update the user's roles (mechanism is not over-restricted).
|
|
@@ -132,7 +132,7 @@ export const self_admin_role_assignment_tests = async ({ sdk } : { sdk: Session,
|
|
|
132
132
|
assert(
|
|
133
133
|
JSON.stringify(afterAdminUpdate.roles) === JSON.stringify([NON_ADMIN_ROLE]),
|
|
134
134
|
`Admin role update failed: roles are ${JSON.stringify(afterAdminUpdate.roles)}, expected [${NON_ADMIN_ROLE}]`,
|
|
135
|
-
'
|
|
135
|
+
'admin can manage user roles (positive control)',
|
|
136
136
|
)
|
|
137
137
|
} finally {
|
|
138
138
|
// Cleanup: delete the throwaway attacker. Never touches pre-existing users.
|
|
@@ -155,11 +155,11 @@ if (require.main === module) {
|
|
|
155
155
|
|
|
156
156
|
runTests()
|
|
157
157
|
.then(() => {
|
|
158
|
-
console.log("✅
|
|
158
|
+
console.log("✅ self-admin role assignment test suite completed successfully")
|
|
159
159
|
process.exit(0)
|
|
160
160
|
})
|
|
161
161
|
.catch((error) => {
|
|
162
|
-
console.error("❌
|
|
162
|
+
console.error("❌ self-admin role assignment test suite failed:", error)
|
|
163
163
|
process.exit(1)
|
|
164
164
|
})
|
|
165
165
|
}
|
|
@@ -8,7 +8,7 @@ import { PROVIDER_PERMISSIONS } from "@tellescope/constants"
|
|
|
8
8
|
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Regression test
|
|
11
|
+
* Regression test — `calendar_events.startLinkToken` privilege escalation.
|
|
12
12
|
*
|
|
13
13
|
* startLinkToken is a DOCUMENTED feature ("Video Call Start Links"): a tenant's staff / API-key
|
|
14
14
|
* integration PATCHes a CalendarEvent to set a random token, then sends the host user the
|
|
@@ -40,7 +40,7 @@ const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
|
40
40
|
const RAND = () => Math.random().toString(36).slice(2, 10)
|
|
41
41
|
|
|
42
42
|
export const startlinktoken_escalation_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
43
|
-
log_header("
|
|
43
|
+
log_header("startLinkToken enduser-writable / readable → staff session escalation")
|
|
44
44
|
|
|
45
45
|
const businessId = sdk.userInfo.businessId
|
|
46
46
|
const userIds: string[] = []
|
|
@@ -66,7 +66,7 @@ export const startlinktoken_escalation_tests = async ({ sdk } : { sdk: Session,
|
|
|
66
66
|
enduserIds.push(patient.id)
|
|
67
67
|
|
|
68
68
|
const event = await sdk.api.calendar_events.createOne({
|
|
69
|
-
title: '
|
|
69
|
+
title: 'appointment', durationInMinutes: 30, startTimeInMS: Date.now() + 3600_000,
|
|
70
70
|
attendees: [{ type: 'user', id: host_user.id }, { type: 'enduser', id: patient.id }],
|
|
71
71
|
} as any)
|
|
72
72
|
eventIds.push(event.id)
|
|
@@ -140,7 +140,7 @@ export const startlinktoken_escalation_tests = async ({ sdk } : { sdk: Session,
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Report the full table before asserting (the harness exits on the first failed assert).
|
|
143
|
-
console.log('\n
|
|
143
|
+
console.log('\n check summary:')
|
|
144
144
|
for (const c of checks) console.log(` ${c.pass ? 'PASS' : 'FAIL'} ${c.label} — ${c.detail}`)
|
|
145
145
|
console.log('')
|
|
146
146
|
for (const c of checks) assert(c.pass, `${c.label} — ${c.detail}`, c.label)
|
|
@@ -157,6 +157,6 @@ if (require.main === module) {
|
|
|
157
157
|
const sdkNonAdmin = new Session({ host })
|
|
158
158
|
setup_tests(sdk, sdkNonAdmin)
|
|
159
159
|
.then(() => startlinktoken_escalation_tests({ sdk, sdkNonAdmin }))
|
|
160
|
-
.then(() => { console.log("✅
|
|
161
|
-
.catch((error) => { console.error("❌
|
|
160
|
+
.then(() => { console.log("✅ startLinkToken escalation test suite completed successfully"); process.exit(0) })
|
|
161
|
+
.catch((error) => { console.error("❌ startLinkToken escalation test suite failed:", error); process.exit(1) })
|
|
162
162
|
}
|
|
@@ -51,7 +51,7 @@ const assert_local_api = () => {
|
|
|
51
51
|
const hostname = new URL(host).hostname
|
|
52
52
|
if (!LOCAL_HOSTNAMES.includes(hostname)) {
|
|
53
53
|
throw new Error(
|
|
54
|
-
`
|
|
54
|
+
`SSRF tests must run against a local API (got ${host}). `
|
|
55
55
|
+ `127.0.0.1 would resolve to the API server rather than this machine, and this test configures a webhook.`
|
|
56
56
|
)
|
|
57
57
|
}
|
|
@@ -76,8 +76,7 @@ const wait_until = async (predicate: () => boolean, ms: number) => {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
* Regression tests
|
|
80
|
-
* (security-audit/findings/F-0015-webhook-outbound-ssrf.md).
|
|
79
|
+
* Regression tests: outbound webhook delivery must refuse internal destinations.
|
|
81
80
|
*
|
|
82
81
|
* Outbound webhooks request customer-configured URLs that are validated only for SYNTAX
|
|
83
82
|
* (urlValidator -> validator.isURL, which happily accepts http://10.0.0.1/). Four call sites are
|
|
@@ -99,7 +98,7 @@ const wait_until = async (predicate: () => boolean, ms: number) => {
|
|
|
99
98
|
* subscription. So each is paired with a positive control that proves the attempt really was made.
|
|
100
99
|
*/
|
|
101
100
|
export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
102
|
-
log_header("
|
|
101
|
+
log_header("outbound webhook SSRF")
|
|
103
102
|
assert_local_api()
|
|
104
103
|
|
|
105
104
|
const SENTINEL = `f0015-sentinel-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
@@ -110,9 +109,9 @@ export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkN
|
|
|
110
109
|
// guard refuses in every environment, and loopback does not qualify (see the note above).
|
|
111
110
|
assert(
|
|
112
111
|
false,
|
|
113
|
-
'
|
|
112
|
+
'SKIPPED: no non-loopback RFC1918 interface on this host, so the SSRF sentinel cannot be '
|
|
114
113
|
+ 'bound anywhere the guard refuses. Run on a host with a private network interface.',
|
|
115
|
-
'
|
|
114
|
+
'outbound SSRF',
|
|
116
115
|
)
|
|
117
116
|
return
|
|
118
117
|
}
|
|
@@ -179,21 +178,21 @@ export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkN
|
|
|
179
178
|
// webhook, every "sentinel not contacted" assertion below is meaningless.
|
|
180
179
|
// ---------------------------------------------------------------------------------------------
|
|
181
180
|
await async_test(
|
|
182
|
-
'
|
|
181
|
+
'automation webhook to a permitted target still delivers (regression baseline)',
|
|
183
182
|
() => send_automation_webhook_to(okURL),
|
|
184
183
|
{ onResult: (r: any) => r.okHits === 1 && r.sentinelHits === 0 },
|
|
185
184
|
)
|
|
186
185
|
|
|
187
186
|
// T3 — direct non-allowlisted private destination.
|
|
188
187
|
await async_test(
|
|
189
|
-
'
|
|
188
|
+
'automation webhook does not reach a private destination',
|
|
190
189
|
() => send_automation_webhook_to(sentinelURL),
|
|
191
190
|
{ onResult: (r: any) => r.sentinelHits === 0 },
|
|
192
191
|
)
|
|
193
192
|
|
|
194
193
|
// T4 — the redirect bypass. THE test that justifies a connect-time guard over a pre-flight check.
|
|
195
194
|
await async_test(
|
|
196
|
-
'
|
|
195
|
+
'automation webhook does not follow a redirect from a permitted host into a private one',
|
|
197
196
|
() => send_automation_webhook_to(redirectURL),
|
|
198
197
|
{
|
|
199
198
|
// redirectHits === 1 is the positive control: hop 1 was allowlisted and really was reached, so
|
|
@@ -212,7 +211,7 @@ export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkN
|
|
|
212
211
|
templateEnduserId = templateEnduser.id
|
|
213
212
|
|
|
214
213
|
await async_test(
|
|
215
|
-
'
|
|
214
|
+
'automation webhook validates the URL after template interpolation, not before',
|
|
216
215
|
() => send_automation_webhook_to('http://{{enduser.ssrfTarget}}/ssrf-sentinel', templateEnduser.id),
|
|
217
216
|
{ onResult: (r: any) => r.sentinelHits === 0 },
|
|
218
217
|
)
|
|
@@ -229,7 +228,7 @@ export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkN
|
|
|
229
228
|
configuredWebhook = true
|
|
230
229
|
|
|
231
230
|
await async_test(
|
|
232
|
-
'
|
|
231
|
+
'configured webhook to a permitted target still delivers (positive control)',
|
|
233
232
|
async () => {
|
|
234
233
|
sentinelHits = 0; okHits = 0
|
|
235
234
|
const e = await sdk.api.endusers.createOne({ email: `f0015-ok-${Date.now()}@example.com` })
|
|
@@ -244,7 +243,7 @@ export const webhook_outbound_ssrf_tests = async ({ sdk } : { sdk: Session, sdkN
|
|
|
244
243
|
await sdk.api.webhooks.update({ url: sentinelURL })
|
|
245
244
|
|
|
246
245
|
await async_test(
|
|
247
|
-
'
|
|
246
|
+
'configured webhook does not deliver to a private destination',
|
|
248
247
|
async () => {
|
|
249
248
|
sentinelHits = 0; okHits = 0
|
|
250
249
|
const e = await sdk.api.endusers.createOne({ email: `f0015-blocked-${Date.now()}@example.com` })
|
|
@@ -282,11 +281,11 @@ if (require.main === module) {
|
|
|
282
281
|
|
|
283
282
|
runTests()
|
|
284
283
|
.then(() => {
|
|
285
|
-
console.log("✅
|
|
284
|
+
console.log("✅ outbound webhook SSRF test suite completed successfully")
|
|
286
285
|
process.exit(0)
|
|
287
286
|
})
|
|
288
287
|
.catch((error) => {
|
|
289
|
-
console.error("❌
|
|
288
|
+
console.error("❌ outbound webhook SSRF test suite failed:", error)
|
|
290
289
|
process.exit(1)
|
|
291
290
|
})
|
|
292
291
|
}
|
package/src/tests/api_tests/security/{F-0155-webhook-timeout.test.ts → webhook-timeout.test.ts}
RENAMED
|
@@ -10,12 +10,11 @@ import { setup_tests } from "../../setup"
|
|
|
10
10
|
|
|
11
11
|
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
12
12
|
|
|
13
|
-
// The server-side axios timeout is 15s (
|
|
13
|
+
// The server-side axios timeout is 15s (the fix under test). We bound our own wait above that.
|
|
14
14
|
const CLIENT_WAIT_MS = 20_000
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Regression test
|
|
18
|
-
* (security-audit/findings/F-0155-outbound-webhook-no-timeout-slow-loris.md).
|
|
17
|
+
* Regression test: outbound webhook delivery must time out rather than hang.
|
|
19
18
|
*
|
|
20
19
|
* Outbound webhook delivery used axios.post(url, ...) with no `timeout` (axios default = infinite).
|
|
21
20
|
* A tenant-controlled receiver that accepts the connection and never responds hangs the awaited
|
|
@@ -28,7 +27,7 @@ const CLIENT_WAIT_MS = 20_000
|
|
|
28
27
|
* - red (unfixed): the call is still pending at 20s → client timer wins
|
|
29
28
|
*/
|
|
30
29
|
export const webhook_timeout_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
31
|
-
log_header("
|
|
30
|
+
log_header("outbound webhook timeout")
|
|
32
31
|
|
|
33
32
|
let enduserId: string | undefined
|
|
34
33
|
const slowServer = http.createServer(() => { /* accept connection, never respond */ })
|
|
@@ -42,7 +41,7 @@ export const webhook_timeout_tests = async ({ sdk } : { sdk: Session, sdkNonAdmi
|
|
|
42
41
|
enduserId = enduser.id
|
|
43
42
|
|
|
44
43
|
await async_test(
|
|
45
|
-
'
|
|
44
|
+
'outbound webhook to a non-responding receiver times out server-side (does not hang)',
|
|
46
45
|
async () => {
|
|
47
46
|
const start = Date.now()
|
|
48
47
|
const outcome = await Promise.race([
|
|
@@ -75,11 +74,11 @@ if (require.main === module) {
|
|
|
75
74
|
|
|
76
75
|
runTests()
|
|
77
76
|
.then(() => {
|
|
78
|
-
console.log("✅
|
|
77
|
+
console.log("✅ webhook timeout test suite completed successfully")
|
|
79
78
|
process.exit(0)
|
|
80
79
|
})
|
|
81
80
|
.catch((error) => {
|
|
82
|
-
console.error("❌
|
|
81
|
+
console.error("❌ webhook timeout test suite failed:", error)
|
|
83
82
|
process.exit(1)
|
|
84
83
|
})
|
|
85
84
|
}
|
|
@@ -133,7 +133,7 @@ export const webhook_error_handling_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
133
133
|
|
|
134
134
|
// Scenario 3 (timeout): skipped — WEBHOOK_TIMEOUT_MS is a constant (15s), not env-overridable,
|
|
135
135
|
// and a 15s+ non-responding receiver would make this suite unacceptably slow.
|
|
136
|
-
// Covered by security/
|
|
136
|
+
// Covered by security/webhook-timeout.test.ts via send_automation_webhook.
|
|
137
137
|
|
|
138
138
|
// Scenario 4: success path unchanged — 200 logged, counter untouched
|
|
139
139
|
await sdk.api.webhooks.update({ url: `${receiverBase}/success` })
|