@tellescope/sdk 1.256.13 → 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 +20 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.js +272 -0
- package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -0
- 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-0179-booking-info-idor-token-mint.test.d.ts +30 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +336 -0
- package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +354 -0
- package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +389 -0
- package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +324 -0
- package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +282 -0
- package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
- 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/tickets_bulk_assign_from_queue.test.d.ts +5 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +329 -0
- package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.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/setup_isolated.d.ts +68 -0
- package/lib/cjs/tests/setup_isolated.d.ts.map +1 -0
- package/lib/cjs/tests/setup_isolated.js +232 -0
- package/lib/cjs/tests/setup_isolated.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +287 -227
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.js +89 -0
- package/lib/cjs/tests/unit_tests/do_not_send_address.test.js.map +1 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +140 -0
- package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
- package/lib/cjs/tsconfig.cjs.tsbuildinfo +1 -0
- 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 +20 -0
- package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/organization_signup.test.js +264 -0
- package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -0
- 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-0179-booking-info-idor-token-mint.test.d.ts +30 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +332 -0
- package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +350 -0
- package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +382 -0
- package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +317 -0
- package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +277 -0
- package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
- 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/tickets_bulk_assign_from_queue.test.d.ts +5 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +324 -0
- package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.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/setup_isolated.d.ts +68 -0
- package/lib/esm/tests/setup_isolated.d.ts.map +1 -0
- package/lib/esm/tests/setup_isolated.js +221 -0
- package/lib/esm/tests/setup_isolated.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +287 -227
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.js +85 -0
- package/lib/esm/tests/unit_tests/do_not_send_address.test.js.map +1 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +113 -0
- package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
- 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 +240 -0
- package/src/tests/api_tests/purchase_made_trigger.test.ts +29 -41
- package/src/tests/api_tests/security/add-to-journey-foreign-enduserid.test.ts +207 -0
- 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/automated-actions-process-gate.test.ts +237 -0
- package/src/tests/api_tests/security/booking-info-idor-token-mint.test.ts +224 -0
- 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/schema-adminonly-enforcement.test.ts +287 -0
- 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/startlinktoken-enduser-staff-escalation.test.ts +162 -0
- 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/tickets_bulk_assign_from_queue.test.ts +279 -0
- package/src/tests/api_tests/webhook_error_handling.test.ts +1 -1
- package/src/tests/setup_isolated.ts +170 -0
- package/src/tests/tests.ts +60 -18
- package/src/tests/unit_tests/do_not_send_address.test.ts +60 -0
- package/src/tests/unit_tests/openapi_spec_freshness.test.ts +88 -0
- package/test_generated.pdf +0 -0
- package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +0 -301
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import axios from "axios"
|
|
4
|
+
|
|
5
|
+
import { Session } from "../../../sdk"
|
|
6
|
+
import {
|
|
7
|
+
assert,
|
|
8
|
+
log_header,
|
|
9
|
+
} from "@tellescope/testing"
|
|
10
|
+
import { PROVIDER_PERMISSIONS, PLACEHOLDER_ID } from "@tellescope/constants"
|
|
11
|
+
import { setup_tests } from "../../setup"
|
|
12
|
+
|
|
13
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
14
|
+
|
|
15
|
+
const RAND = () => Math.random().toString(36).slice(2, 10)
|
|
16
|
+
|
|
17
|
+
const ALL = 'All' as const
|
|
18
|
+
const FULL_ACCESS = { create: ALL, read: ALL, update: ALL, delete: ALL }
|
|
19
|
+
|
|
20
|
+
const PROCESS_PATH = '/v1/automated-actions/process'
|
|
21
|
+
|
|
22
|
+
// A single collected check. Everything is gathered first and asserted at the end, because the
|
|
23
|
+
// testing harness exits the process on the first failed assert — collecting first means a RED run
|
|
24
|
+
// prints the full evidence table (and reaches `finally` cleanup) before the first assert exits.
|
|
25
|
+
type Check = {
|
|
26
|
+
label: string,
|
|
27
|
+
pass: boolean,
|
|
28
|
+
detail: string,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Regression test: `automated_actions.process` must be admin-gated, single-fire and audited.
|
|
33
|
+
*
|
|
34
|
+
* `automated_actions.process` (api.ts:3902) is the WORKER's action runner — its schema description
|
|
35
|
+
* reads "Used by worker for new action types". It was registered with only
|
|
36
|
+
* `{ requiresSubscription: true }`, so the framework gated it on the ordinary
|
|
37
|
+
* `automated_actions.update` verb, which the default provider role holds ('Assigned'). The handler
|
|
38
|
+
* then dispatched `automatedAction.action` through a 14-branch if-chain reaching real vendors:
|
|
39
|
+
* chargebeeChargeCardOnFile (card charge), belugaAutoRx / belugaTriggerRefill / mdiTriggerRefill
|
|
40
|
+
* (prescriptions and refills), createScriptSureDraft (medication draft), healthiePushForms /
|
|
41
|
+
* metriportPushFormResponse (PHI export). It checked neither the row's `status` nor whether it had
|
|
42
|
+
* already run, so any row was replayable: MDI advances to the next scheduled refill each call,
|
|
43
|
+
* Chargebee charges again. And there was no storeUserLog, so the action was attributed to the
|
|
44
|
+
* worker rather than the caller.
|
|
45
|
+
*
|
|
46
|
+
* The fix has three parts:
|
|
47
|
+
* 1. `adminOnly: true` on the api.ts registration (routing.ts:944 reads the flag from that
|
|
48
|
+
* options object — a schema-only flag is inert, which is a separate issue).
|
|
49
|
+
* 2. A single-fire claim for human callers: an atomic findOneAndUpdate with `status: 'active'` in
|
|
50
|
+
* the filter, stamping `finished` + `processedAt` before the vendor dispatch. The worker
|
|
51
|
+
* (`wasAutomated`) is exempt because its own claim in
|
|
52
|
+
* worker/modules/database.ts:114 is already atomic and it needs the row to stay 'active'
|
|
53
|
+
* across the call so a retryable failure (worker/modules/automations.ts:2053) can be
|
|
54
|
+
* re-claimed after the lock times out.
|
|
55
|
+
* 3. storeUserLog on the human path, recording the caller and the automatedActionId.
|
|
56
|
+
*
|
|
57
|
+
* The seeded row uses `setEnduserStatus`, which is NOT one of the API's if-chain branches, so it
|
|
58
|
+
* falls through to `{ success: false, unimplemented: true }` (api.ts) — no vendor call, no Bedrock,
|
|
59
|
+
* no PHI export. `processAfter` is set an hour out so the live worker cannot race the row (the same
|
|
60
|
+
* trick mdi_trigger_refill / beluga_trigger_refill use).
|
|
61
|
+
*
|
|
62
|
+
* RED (pre-fix): A1 fails (a non-admin holding only `automated_actions.update` reaches the handler)
|
|
63
|
+
* and C1 fails (the second call on the same row executes again instead of being rejected).
|
|
64
|
+
* B1 (admin positive control) passes pre-fix and must keep passing.
|
|
65
|
+
* GREEN (post-fix): all five checks pass.
|
|
66
|
+
*/
|
|
67
|
+
export const automated_actions_process_gate_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
68
|
+
log_header("automated_actions.process must be admin-only and single-fire")
|
|
69
|
+
|
|
70
|
+
const checks: Check[] = []
|
|
71
|
+
const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
|
|
72
|
+
|
|
73
|
+
// Raw axios: the SDK's parseError discards the HTTP status, and the whole point here is WHICH
|
|
74
|
+
// status the gate returns.
|
|
75
|
+
const post = async (authToken: string, automatedActionId: string) => {
|
|
76
|
+
const r = await axios.request({
|
|
77
|
+
method: 'post',
|
|
78
|
+
url: `${host}${PROCESS_PATH}`,
|
|
79
|
+
data: { automatedActionId },
|
|
80
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
81
|
+
validateStatus: () => true,
|
|
82
|
+
})
|
|
83
|
+
return { status: r.status, body: r.data }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let roleId = ''
|
|
87
|
+
let attackerId = ''
|
|
88
|
+
let enduserId = ''
|
|
89
|
+
const automatedActionIds: string[] = []
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
/* ================= fixtures ================= */
|
|
93
|
+
|
|
94
|
+
// A non-admin role that explicitly HOLDS automated_actions update — the point of the finding is
|
|
95
|
+
// that holding the declared verb was enough to fire vendor actions.
|
|
96
|
+
const role = `f0192-automations-${RAND()}`
|
|
97
|
+
const rbap = await sdk.api.role_based_access_permissions.createOne({
|
|
98
|
+
role,
|
|
99
|
+
permissions: { ...PROVIDER_PERMISSIONS, automated_actions: FULL_ACCESS },
|
|
100
|
+
} as any)
|
|
101
|
+
roleId = rbap.id
|
|
102
|
+
|
|
103
|
+
// Throwaway staff user — never mutate an existing user's roles. verifiedEmail must be set at
|
|
104
|
+
// create time (it is rejected on update), otherwise calls are blocked on email verification
|
|
105
|
+
// rather than on the gate under test.
|
|
106
|
+
const attacker = await sdk.api.users.createOne({
|
|
107
|
+
email: `f0192-attacker-${RAND()}@tellescope.example`,
|
|
108
|
+
fname: 'F0192', lname: 'Staff',
|
|
109
|
+
verifiedEmail: true,
|
|
110
|
+
notificationEmailsDisabled: true,
|
|
111
|
+
roles: [role],
|
|
112
|
+
} as any)
|
|
113
|
+
attackerId = attacker.id
|
|
114
|
+
|
|
115
|
+
// Admin-minted token, not .authenticate — /login is IP rate-limited and the suite shares an IP.
|
|
116
|
+
const attackerToken = (await sdk.api.users.generate_auth_token({ id: attackerId })).authToken
|
|
117
|
+
const adminToken = (await sdk.api.users.generate_auth_token({ id: sdk.userInfo.id })).authToken
|
|
118
|
+
|
|
119
|
+
const attackerSDK = new Session({ host, authToken: attackerToken })
|
|
120
|
+
await attackerSDK.refresh_session()
|
|
121
|
+
record(
|
|
122
|
+
'setup: attacker session is a non-admin holding automated_actions.update',
|
|
123
|
+
attackerSDK.userInfo.id === attackerId && !(attackerSDK.userInfo.roles ?? []).includes('Admin'),
|
|
124
|
+
`id=${attackerSDK.userInfo.id} roles=${JSON.stringify(attackerSDK.userInfo.roles)}`,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const enduser = await sdk.api.endusers.createOne({ fname: 'F0192', lname: 'Patient' })
|
|
128
|
+
enduserId = enduser.id
|
|
129
|
+
|
|
130
|
+
// `setEnduserStatus` is handled by the worker, NOT by the API's process if-chain, so it falls
|
|
131
|
+
// through to `unimplemented: true`. processAfter is an hour out so the live worker cannot claim
|
|
132
|
+
// the row before the explicit calls below.
|
|
133
|
+
const seedAction = async () => {
|
|
134
|
+
const a = await sdk.api.automated_actions.createOne({
|
|
135
|
+
enduserId,
|
|
136
|
+
journeyId: PLACEHOLDER_ID,
|
|
137
|
+
automationStepId: PLACEHOLDER_ID,
|
|
138
|
+
event: { type: 'onJourneyStart', info: {} },
|
|
139
|
+
action: { type: 'setEnduserStatus', info: { status: 'Working' } },
|
|
140
|
+
status: 'active',
|
|
141
|
+
processAfter: Date.now() + 60 * 60 * 1000,
|
|
142
|
+
} as any)
|
|
143
|
+
automatedActionIds.push(a.id)
|
|
144
|
+
return a
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ===== A: the privilege gap — a non-admin with the declared verb must be rejected ===== */
|
|
148
|
+
|
|
149
|
+
const attackerRow = await seedAction()
|
|
150
|
+
const attackerAttempt = await post(attackerToken, attackerRow.id)
|
|
151
|
+
|
|
152
|
+
// Baseline on the same path with an admin token, so a blanket 401 (expired subscription,
|
|
153
|
+
// bad route, broken fixture) cannot masquerade as a working gate.
|
|
154
|
+
const baselineRow = await seedAction()
|
|
155
|
+
const baseline = await post(adminToken, baselineRow.id)
|
|
156
|
+
const badProbe = baseline.status === 401 || baseline.status === 403
|
|
157
|
+
|
|
158
|
+
record(
|
|
159
|
+
'A1: a non-admin holding automated_actions.update cannot call process',
|
|
160
|
+
!badProbe && (attackerAttempt.status === 401 || attackerAttempt.status === 403),
|
|
161
|
+
`attacker=${attackerAttempt.status} ${JSON.stringify(attackerAttempt.body)} baseline(admin)=${baseline.status}`
|
|
162
|
+
+ (badProbe ? ' BAD PROBE: admin baseline was also rejected' : ''),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// The attacker's row must be untouched — the gate has to fire before the claim and the dispatch.
|
|
166
|
+
const attackerRowAfter = await sdk.api.automated_actions.getOne(attackerRow.id)
|
|
167
|
+
record(
|
|
168
|
+
'A2: a rejected non-admin call leaves the row unfired (status still active)',
|
|
169
|
+
attackerRowAfter.status === 'active',
|
|
170
|
+
`status=${attackerRowAfter.status} (expected active)`,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
/* ===== B: positive control — an admin can still call it ===== */
|
|
174
|
+
|
|
175
|
+
record(
|
|
176
|
+
'B1: an admin can still process a pending row',
|
|
177
|
+
baseline.status === 200 && baseline.body?.unimplemented === true,
|
|
178
|
+
`status=${baseline.status} body=${JSON.stringify(baseline.body)}`,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
/* ===== C: the replay gate — the same row must not fire twice ===== */
|
|
182
|
+
|
|
183
|
+
const replay = await post(adminToken, baselineRow.id)
|
|
184
|
+
record(
|
|
185
|
+
'C1: processing the same row twice is rejected (no replay)',
|
|
186
|
+
replay.status === 409,
|
|
187
|
+
`second call status=${replay.status} body=${JSON.stringify(replay.body)} (expected 409)`,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const baselineRowAfter = await sdk.api.automated_actions.getOne(baselineRow.id)
|
|
191
|
+
record(
|
|
192
|
+
'C2: a processed row is marked finished so neither a replay nor the worker re-runs it',
|
|
193
|
+
baselineRowAfter.status === 'finished',
|
|
194
|
+
`status=${baselineRowAfter.status} (expected finished)`,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
/* ===== report: print full table first, then assert (harness exits on first failure) ===== */
|
|
198
|
+
console.log('\n check summary:')
|
|
199
|
+
for (const c of checks) {
|
|
200
|
+
console.log(` ${c.pass ? 'PASS' : 'FAIL'} ${c.label} — ${c.detail}`)
|
|
201
|
+
}
|
|
202
|
+
console.log('')
|
|
203
|
+
|
|
204
|
+
for (const c of checks) {
|
|
205
|
+
assert(c.pass, `${c.label} — ${c.detail}`, c.label)
|
|
206
|
+
}
|
|
207
|
+
} finally {
|
|
208
|
+
// Reverse order. The automated_actions go first: they hang off enduserId with
|
|
209
|
+
// onDependencyDelete 'delete', so deleting the enduser would take them with it.
|
|
210
|
+
for (const id of automatedActionIds) { try { await sdk.api.automated_actions.deleteOne(id) } catch {} }
|
|
211
|
+
if (enduserId) { try { await sdk.api.endusers.deleteOne(enduserId) } catch {} }
|
|
212
|
+
if (attackerId) { try { await sdk.api.users.deleteOne(attackerId) } catch {} }
|
|
213
|
+
if (roleId) { try { await sdk.api.role_based_access_permissions.deleteOne(roleId) } catch {} }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Allow running this test file independently
|
|
218
|
+
if (require.main === module) {
|
|
219
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
220
|
+
const sdk = new Session({ host })
|
|
221
|
+
const sdkNonAdmin = new Session({ host })
|
|
222
|
+
|
|
223
|
+
const runTests = async () => {
|
|
224
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
225
|
+
await automated_actions_process_gate_tests({ sdk, sdkNonAdmin })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
runTests()
|
|
229
|
+
.then(() => {
|
|
230
|
+
console.log("✅ automated_actions.process gate test suite completed successfully")
|
|
231
|
+
process.exit(0)
|
|
232
|
+
})
|
|
233
|
+
.catch((error) => {
|
|
234
|
+
console.error("❌ automated_actions.process gate test suite failed:", error)
|
|
235
|
+
process.exit(1)
|
|
236
|
+
})
|
|
237
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session, EnduserSession } from "../../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
assert,
|
|
6
|
+
log_header,
|
|
7
|
+
} from "@tellescope/testing"
|
|
8
|
+
import { authenticate_enduser_via_token, setup_tests } from "../../setup"
|
|
9
|
+
|
|
10
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
11
|
+
const businessId = '60398b1131a295e64f084ff6'
|
|
12
|
+
|
|
13
|
+
const RAND = () => Math.random().toString(36).slice(2, 10)
|
|
14
|
+
|
|
15
|
+
const VICTIM_STATE = 'CA'
|
|
16
|
+
const ATTACKER_STATE_WRITE = 'NY'
|
|
17
|
+
|
|
18
|
+
type Check = {
|
|
19
|
+
label: string,
|
|
20
|
+
pass: boolean,
|
|
21
|
+
detail: string,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Regression test: `form_fields.booking_info` must not mint a booking token for another patient.
|
|
26
|
+
*
|
|
27
|
+
* `form_fields.booking_info` resolved its target patient as
|
|
28
|
+
* `DB.endusers.findById(enduserId || req.session.id)` with no ownership check for enduser
|
|
29
|
+
* sessions. The enduser DB branch builds the endusers collection with `enduserAccess: true`
|
|
30
|
+
* (database.ts), so a FOREIGN in-tenant id resolves — and the handler then writes
|
|
31
|
+
* `enduserFields.state` onto that record and calls `login_enduser` with
|
|
32
|
+
* `sessionScopes: ['appointment-booking']`, returning a live 24h token for the victim inside
|
|
33
|
+
* `bookingURL`. booking_info is enduser-callable AND granted to the 'public-form' scope, so a
|
|
34
|
+
* portal patient or an anonymous form-filler could mint a working session as any co-tenant patient.
|
|
35
|
+
*
|
|
36
|
+
* The fix mirrors `prepare_form_response` (api.ts): reject when an enduser session supplies an
|
|
37
|
+
* `enduserId` other than its own, before the booking-page lookup and before the `state` write.
|
|
38
|
+
*
|
|
39
|
+
* RED (pre-fix): A1 and A2 fail — the exploit returns 200 with a bookingURL whose token decodes to
|
|
40
|
+
* the VICTIM's id, and the victim's `state` is overwritten. B/C (positive controls) pass pre-fix.
|
|
41
|
+
* GREEN (post-fix): all six checks pass.
|
|
42
|
+
*
|
|
43
|
+
* Checks are collected and printed before any assert runs: the testing harness has
|
|
44
|
+
* EXIT_ON_FIRST_ERROR, so asserting inline would kill the process before the evidence table and
|
|
45
|
+
* before `finally` cleanup.
|
|
46
|
+
*/
|
|
47
|
+
export const booking_info_idor_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
48
|
+
log_header("booking_info must not mint an appointment-booking token for another enduser")
|
|
49
|
+
|
|
50
|
+
const checks: Check[] = []
|
|
51
|
+
const record = (label: string, pass: boolean, detail: string) => checks.push({ label, pass, detail })
|
|
52
|
+
|
|
53
|
+
// The minted JWT carries `id: enduser._id.toString()` (authentication.ts login_enduser). Decode
|
|
54
|
+
// it straight out of the returned URL — the whole point of the finding is WHOSE session it is.
|
|
55
|
+
const token_enduser_id = (bookingURL: string) => {
|
|
56
|
+
const token = /[?&]token=([^&]+)/.exec(bookingURL || '')?.[1]
|
|
57
|
+
const payload = token?.split('.')[1]
|
|
58
|
+
if (!payload) return ''
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(Buffer.from(payload, 'base64').toString()).id as string
|
|
61
|
+
} catch {
|
|
62
|
+
return ''
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let templateId = ''
|
|
67
|
+
let bookingPageId = ''
|
|
68
|
+
const enduserIds: string[] = []
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
/* ================= fixtures ================= */
|
|
72
|
+
|
|
73
|
+
const template = await sdk.api.calendar_event_templates.createOne({
|
|
74
|
+
title: `F0179 template ${RAND()}`, durationInMinutes: 30,
|
|
75
|
+
} as any)
|
|
76
|
+
templateId = template.id
|
|
77
|
+
|
|
78
|
+
const bookingPage = await sdk.api.appointment_booking_pages.createOne({
|
|
79
|
+
title: `F0179 booking page ${RAND()}`,
|
|
80
|
+
calendarEventTemplateIds: [templateId],
|
|
81
|
+
locationIds: [],
|
|
82
|
+
} as any)
|
|
83
|
+
bookingPageId = bookingPage.id
|
|
84
|
+
|
|
85
|
+
const attacker = await sdk.api.endusers.createOne({
|
|
86
|
+
email: `f0179-attacker-${RAND()}@tellescope.example`, fname: 'F0179', lname: 'Attacker',
|
|
87
|
+
})
|
|
88
|
+
enduserIds.push(attacker.id)
|
|
89
|
+
|
|
90
|
+
const victim = await sdk.api.endusers.createOne({
|
|
91
|
+
email: `f0179-victim-${RAND()}@tellescope.example`, fname: 'F0179', lname: 'Victim',
|
|
92
|
+
state: VICTIM_STATE,
|
|
93
|
+
})
|
|
94
|
+
enduserIds.push(victim.id)
|
|
95
|
+
|
|
96
|
+
// Token mint, NOT enduserSDK.authenticate — /login-enduser is IP rate-limited and the suite
|
|
97
|
+
// shares one IP.
|
|
98
|
+
const attackerSDK = new EnduserSession({ host, businessId })
|
|
99
|
+
await authenticate_enduser_via_token(sdk, attackerSDK, { id: attacker.id })
|
|
100
|
+
|
|
101
|
+
/* ===== A: the exploit — enduser mints a booking session as another patient ===== */
|
|
102
|
+
|
|
103
|
+
let a1Detail = ''
|
|
104
|
+
let a1Pass = false
|
|
105
|
+
try {
|
|
106
|
+
const r = await attackerSDK.api.form_fields.booking_info({ bookingPageId, enduserId: victim.id })
|
|
107
|
+
const mintedFor = token_enduser_id(r.bookingURL)
|
|
108
|
+
a1Detail = (
|
|
109
|
+
`EXPLOITED: 200 with a bookingURL; token subject=${mintedFor} `
|
|
110
|
+
+ `(victim=${victim.id}, attacker=${attacker.id})`
|
|
111
|
+
+ (mintedFor === victim.id ? ' — a live appointment-booking session AS THE VICTIM' : '')
|
|
112
|
+
)
|
|
113
|
+
} catch (e: any) {
|
|
114
|
+
a1Pass = true
|
|
115
|
+
a1Detail = `rejected: ${e?.message || e?.toString()}`
|
|
116
|
+
}
|
|
117
|
+
record('A1: enduser cannot mint a booking token for another enduser', a1Pass, a1Detail)
|
|
118
|
+
|
|
119
|
+
// A2: the same call also writes enduserFields.state onto the target before minting.
|
|
120
|
+
let a2Attempt = ''
|
|
121
|
+
try {
|
|
122
|
+
await attackerSDK.api.form_fields.booking_info({
|
|
123
|
+
bookingPageId, enduserId: victim.id, enduserFields: { state: ATTACKER_STATE_WRITE },
|
|
124
|
+
})
|
|
125
|
+
a2Attempt = 'call returned 200'
|
|
126
|
+
} catch (e: any) {
|
|
127
|
+
a2Attempt = `call rejected: ${e?.message || e?.toString()}`
|
|
128
|
+
}
|
|
129
|
+
const victimAfter = await sdk.api.endusers.getOne(victim.id)
|
|
130
|
+
record(
|
|
131
|
+
'A2: enduser cannot write `state` onto another enduser via enduserFields',
|
|
132
|
+
victimAfter.state === VICTIM_STATE,
|
|
133
|
+
`${a2Attempt}; victim.state=${victimAfter.state} (expected ${VICTIM_STATE})`,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
/* ===== B: positive controls — the patient's own booking flow still works ===== */
|
|
137
|
+
|
|
138
|
+
let b1Detail = ''
|
|
139
|
+
let b1Pass = false
|
|
140
|
+
try {
|
|
141
|
+
const r = await attackerSDK.api.form_fields.booking_info({ bookingPageId })
|
|
142
|
+
const mintedFor = token_enduser_id(r.bookingURL)
|
|
143
|
+
b1Pass = mintedFor === attacker.id
|
|
144
|
+
b1Detail = `token subject=${mintedFor} (self=${attacker.id})`
|
|
145
|
+
} catch (e: any) {
|
|
146
|
+
b1Detail = `unexpectedly rejected: ${e?.message || e?.toString()}`
|
|
147
|
+
}
|
|
148
|
+
record('B1: enduser can still load booking info with no enduserId', b1Pass, b1Detail)
|
|
149
|
+
|
|
150
|
+
let b2Detail = ''
|
|
151
|
+
let b2Pass = false
|
|
152
|
+
try {
|
|
153
|
+
const r = await attackerSDK.api.form_fields.booking_info({ bookingPageId, enduserId: attacker.id })
|
|
154
|
+
const mintedFor = token_enduser_id(r.bookingURL)
|
|
155
|
+
b2Pass = mintedFor === attacker.id
|
|
156
|
+
b2Detail = `token subject=${mintedFor} (self=${attacker.id})`
|
|
157
|
+
} catch (e: any) {
|
|
158
|
+
b2Detail = `unexpectedly rejected: ${e?.message || e?.toString()}`
|
|
159
|
+
}
|
|
160
|
+
record('B2: enduser can still load booking info for its OWN enduserId', b2Pass, b2Detail)
|
|
161
|
+
|
|
162
|
+
/* ===== C: positive controls — the staff branches are untouched ===== */
|
|
163
|
+
|
|
164
|
+
let c1Detail = ''
|
|
165
|
+
let c1Pass = false
|
|
166
|
+
try {
|
|
167
|
+
const r = await sdk.api.form_fields.booking_info({ bookingPageId, enduserId: victim.id })
|
|
168
|
+
const mintedFor = token_enduser_id(r.bookingURL)
|
|
169
|
+
c1Pass = mintedFor === victim.id
|
|
170
|
+
c1Detail = `token subject=${mintedFor} (target=${victim.id})`
|
|
171
|
+
} catch (e: any) {
|
|
172
|
+
c1Detail = `unexpectedly rejected: ${e?.message || e?.toString()}`
|
|
173
|
+
}
|
|
174
|
+
record('C1: staff can still load booking info for a specific enduser', c1Pass, c1Detail)
|
|
175
|
+
|
|
176
|
+
let c2Detail = ''
|
|
177
|
+
let c2Pass = false
|
|
178
|
+
try {
|
|
179
|
+
const r = await sdk.api.form_fields.booking_info({ bookingPageId })
|
|
180
|
+
c2Pass = !!r.bookingURL && !!r.warningMessage
|
|
181
|
+
c2Detail = `warningMessage=${JSON.stringify(r.warningMessage)} bookingURL=${!!r.bookingURL}`
|
|
182
|
+
} catch (e: any) {
|
|
183
|
+
c2Detail = `unexpectedly rejected: ${e?.message || e?.toString()}`
|
|
184
|
+
}
|
|
185
|
+
record('C2: staff test-contact preview branch still works', c2Pass, c2Detail)
|
|
186
|
+
|
|
187
|
+
/* ===== report: print full table first, then assert (harness exits on first failure) ===== */
|
|
188
|
+
console.log('\n check summary:')
|
|
189
|
+
for (const c of checks) {
|
|
190
|
+
console.log(` ${c.pass ? 'PASS' : 'FAIL'} ${c.label} — ${c.detail}`)
|
|
191
|
+
}
|
|
192
|
+
console.log('')
|
|
193
|
+
|
|
194
|
+
for (const c of checks) {
|
|
195
|
+
assert(c.pass, `${c.label} — ${c.detail}`, c.label)
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
for (const id of enduserIds) { try { await sdk.api.endusers.deleteOne(id) } catch {} }
|
|
199
|
+
if (bookingPageId) { try { await sdk.api.appointment_booking_pages.deleteOne(bookingPageId) } catch {} }
|
|
200
|
+
if (templateId) { try { await sdk.api.calendar_event_templates.deleteOne(templateId) } catch {} }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Allow running this test file independently
|
|
205
|
+
if (require.main === module) {
|
|
206
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
207
|
+
const sdk = new Session({ host })
|
|
208
|
+
const sdkNonAdmin = new Session({ host })
|
|
209
|
+
|
|
210
|
+
const runTests = async () => {
|
|
211
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
212
|
+
await booking_info_idor_tests({ sdk, sdkNonAdmin })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
runTests()
|
|
216
|
+
.then(() => {
|
|
217
|
+
console.log("✅ booking_info IDOR test suite completed successfully")
|
|
218
|
+
process.exit(0)
|
|
219
|
+
})
|
|
220
|
+
.catch((error) => {
|
|
221
|
+
console.error("❌ booking_info IDOR test suite failed:", error)
|
|
222
|
+
process.exit(1)
|
|
223
|
+
})
|
|
224
|
+
}
|
|
@@ -16,14 +16,14 @@ const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
|
16
16
|
const OTHER_TENANT_API_KEY = "ba745e25162bb95a795c5fa1af70df188d93c4d3aac9c48b34a5c8c9dd7b80f7"
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Tenant-boundary guard for cascade_role_rename (relates to
|
|
19
|
+
* Tenant-boundary guard for cascade_role_rename (relates to a prior internal review,
|
|
20
20
|
* which was investigated and closed as a FALSE POSITIVE — see that file for the code trace).
|
|
21
21
|
*
|
|
22
22
|
* The `cascade_role_rename` side-effect handler
|
|
23
23
|
* ([event_handlers_v2/role_based_access_permissions.ts](packages/private/api/api/v1/event_handlers_v2/role_based_access_permissions.ts))
|
|
24
24
|
* runs when a `role_based_access_permissions` doc's `role` field changes. It finds every user
|
|
25
25
|
* with the old role name (via `DBUnrestricted.users`) and rewrites their `roles` array, then
|
|
26
|
-
* deauthenticates them.
|
|
26
|
+
* deauthenticates them. An earlier review hypothesized this query was globally cross-tenant. It is NOT:
|
|
27
27
|
* `DBUnrestricted` bypasses per-user/per-role RBAC but is STILL scoped to the acting session's
|
|
28
28
|
* `businessId` (see `modifyFilterForAccessConstraint` injecting `{ businessId }` at
|
|
29
29
|
* database.ts:1761-1763, reached via the `unrestricted: true` branch at database.ts:2137-2144).
|
|
@@ -42,7 +42,7 @@ const OTHER_TENANT_API_KEY = "ba745e25162bb95a795c5fa1af70df188d93c4d3aac9c48b34
|
|
|
42
42
|
* A collision-proof unique role name (timestamped) is used so the test never touches real roles.
|
|
43
43
|
*/
|
|
44
44
|
export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
45
|
-
log_header("
|
|
45
|
+
log_header("cascade role rename cross-tenant regression")
|
|
46
46
|
|
|
47
47
|
const stamp = Date.now()
|
|
48
48
|
const ROLE_OLD = `XTenantRename_${stamp}`
|
|
@@ -55,7 +55,7 @@ export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Se
|
|
|
55
55
|
let rbapId: string | undefined
|
|
56
56
|
// We create dedicated throwaway users in BOTH tenants and delete them in `finally`. We never
|
|
57
57
|
// mutate any pre-existing user (an earlier version of this test clobbered the apiKey's own
|
|
58
|
-
// admin user via getSome+replaceObjectFields
|
|
58
|
+
// admin user via getSome+replaceObjectFields). controlUser lives in
|
|
59
59
|
// Tenant A and SHOULD be renamed by the same-tenant cascade; victimUser lives in Tenant B and
|
|
60
60
|
// must be UNTOUCHED.
|
|
61
61
|
let controlUserId: string | undefined
|
|
@@ -92,7 +92,7 @@ export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Se
|
|
|
92
92
|
assert(
|
|
93
93
|
victimUser.businessId !== tenantABusinessId,
|
|
94
94
|
`Victim user shares businessId with Tenant A (${victimUser.businessId}) — not a cross-tenant scenario`,
|
|
95
|
-
'
|
|
95
|
+
'setup: tenants are distinct',
|
|
96
96
|
)
|
|
97
97
|
const victimBefore = await sdkOther.api.users.getOne(victimUserId)
|
|
98
98
|
const controlBefore = await sdk.api.users.getOne(controlUserId)
|
|
@@ -100,7 +100,7 @@ export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Se
|
|
|
100
100
|
JSON.stringify(victimBefore.roles) === JSON.stringify([ROLE_OLD])
|
|
101
101
|
&& JSON.stringify(controlBefore.roles) === JSON.stringify([ROLE_OLD]),
|
|
102
102
|
`Setup failed: expected both users to hold [${ROLE_OLD}] (victim=${JSON.stringify(victimBefore.roles)}, control=${JSON.stringify(controlBefore.roles)})`,
|
|
103
|
-
'
|
|
103
|
+
'setup: both users hold ROLE_OLD',
|
|
104
104
|
)
|
|
105
105
|
|
|
106
106
|
// 5. Tenant A renames the role. This triggers cascade_role_rename.
|
|
@@ -113,7 +113,7 @@ export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Se
|
|
|
113
113
|
JSON.stringify(victimAfter.roles) === JSON.stringify([ROLE_OLD]),
|
|
114
114
|
`CROSS-TENANT LEAK: Tenant B victim roles changed to ${JSON.stringify(victimAfter.roles)} `
|
|
115
115
|
+ `after Tenant A renamed its role. Expected [${ROLE_OLD}].`,
|
|
116
|
-
'
|
|
116
|
+
'Tenant B user roles unaffected by other-tenant role rename',
|
|
117
117
|
)
|
|
118
118
|
|
|
119
119
|
// 7. POSITIVE CONTROL — the Tenant A control user SHOULD be renamed (same-tenant cascade intact).
|
|
@@ -122,7 +122,7 @@ export const cascade_role_rename_cross_tenant_tests = async ({ sdk } : { sdk: Se
|
|
|
122
122
|
JSON.stringify(controlAfter.roles) === JSON.stringify([ROLE_NEW]),
|
|
123
123
|
`Same-tenant cascade broken: Tenant A control roles are ${JSON.stringify(controlAfter.roles)}, `
|
|
124
124
|
+ `expected [${ROLE_NEW}].`,
|
|
125
|
-
'
|
|
125
|
+
'Tenant A user roles renamed by same-tenant cascade',
|
|
126
126
|
)
|
|
127
127
|
} finally {
|
|
128
128
|
// Cleanup: delete both throwaway users and the role doc. Never touches pre-existing users.
|
|
@@ -151,11 +151,11 @@ if (require.main === module) {
|
|
|
151
151
|
|
|
152
152
|
runTests()
|
|
153
153
|
.then(() => {
|
|
154
|
-
console.log("✅
|
|
154
|
+
console.log("✅ cascade role rename cross-tenant test suite completed successfully")
|
|
155
155
|
process.exit(0)
|
|
156
156
|
})
|
|
157
157
|
.catch((error) => {
|
|
158
|
-
console.error("❌
|
|
158
|
+
console.error("❌ cascade role rename cross-tenant test suite failed:", error)
|
|
159
159
|
process.exit(1)
|
|
160
160
|
})
|
|
161
161
|
}
|
|
@@ -41,7 +41,7 @@ const collectViolations = (results: { modelName: string, recordId: string, data:
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Regression test
|
|
44
|
+
* Regression test: `/v1/data-sync` redaction.
|
|
45
45
|
*
|
|
46
46
|
* The /v1/data-sync handler must apply the central applyRedactions() pipeline to
|
|
47
47
|
* every non-deleted record. The original bug: redactions were gated behind
|
|
@@ -63,7 +63,7 @@ const collectViolations = (results: { modelName: string, recordId: string, data:
|
|
|
63
63
|
* Post-fix: assertion passes.
|
|
64
64
|
*/
|
|
65
65
|
export const data_sync_redaction_bypass_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
66
|
-
log_header("
|
|
66
|
+
log_header("/v1/data-sync field-redaction bypass regression")
|
|
67
67
|
|
|
68
68
|
const roleName = `f0001-data-sync-bypass-${Date.now()}`
|
|
69
69
|
let testEnduserId: string | undefined
|
|
@@ -124,13 +124,13 @@ export const data_sync_redaction_bypass_tests = async ({ sdk, sdkNonAdmin } : {
|
|
|
124
124
|
// would pass vacuously — guard against that.
|
|
125
125
|
const userRecordsInStream = sync.results.filter(r => r.modelName === 'users').length
|
|
126
126
|
await async_test(
|
|
127
|
-
"
|
|
127
|
+
"guard: /v1/data-sync sync stream contains at least one user record",
|
|
128
128
|
async () => ({ userRecords: userRecordsInStream, totalRecords: sync.results.length }),
|
|
129
129
|
{ onResult: r => r.userRecords >= 1 },
|
|
130
130
|
)
|
|
131
131
|
|
|
132
132
|
await async_test(
|
|
133
|
-
"
|
|
133
|
+
"/v1/data-sync must NOT return hashedPass / hashedPassword / hashedInviteCode",
|
|
134
134
|
async () => ({
|
|
135
135
|
violationCount: violations.length,
|
|
136
136
|
violations: violations.slice(0, 10),
|
|
@@ -143,7 +143,7 @@ export const data_sync_redaction_bypass_tests = async ({ sdk, sdkNonAdmin } : {
|
|
|
143
143
|
// ========================================================================
|
|
144
144
|
// Additional coverage for applyRedactions code paths reachable via /v1/data-sync.
|
|
145
145
|
// Each of these is a distinct branch in applyRedactions (routing.ts:1165-1238)
|
|
146
|
-
// and could regress independently of
|
|
146
|
+
// and could regress independently of this fix.
|
|
147
147
|
// ========================================================================
|
|
148
148
|
|
|
149
149
|
// Case A: schema-level `redactions: ['all']` must apply to ADMIN sessions too.
|
|
@@ -153,7 +153,7 @@ export const data_sync_redaction_bypass_tests = async ({ sdk, sdkNonAdmin } : {
|
|
|
153
153
|
const adminSync = await sdk.sync({ from: new Date(0) })
|
|
154
154
|
const adminViolations = collectViolations(adminSync.results)
|
|
155
155
|
await async_test(
|
|
156
|
-
"
|
|
156
|
+
"coverage A: admin /v1/data-sync also strips redactions:['all'] fields (hashedPass etc.)",
|
|
157
157
|
async () => ({
|
|
158
158
|
violationCount: adminViolations.length,
|
|
159
159
|
violations: adminViolations.slice(0, 10),
|
|
@@ -180,7 +180,7 @@ export const data_sync_redaction_bypass_tests = async ({ sdk, sdkNonAdmin } : {
|
|
|
180
180
|
} catch {}
|
|
181
181
|
}
|
|
182
182
|
await async_test(
|
|
183
|
-
"
|
|
183
|
+
"coverage B: /v1/data-sync strips linkedAccountAccess from other users' records",
|
|
184
184
|
async () => ({
|
|
185
185
|
otherUserRecords: otherUsersInStream.length,
|
|
186
186
|
leakCount: linkedAccountLeaks.length,
|
|
@@ -226,11 +226,11 @@ if (require.main === module) {
|
|
|
226
226
|
|
|
227
227
|
runTests()
|
|
228
228
|
.then(() => {
|
|
229
|
-
console.log("✅
|
|
229
|
+
console.log("✅ data-sync redaction-bypass test suite completed successfully")
|
|
230
230
|
process.exit(0)
|
|
231
231
|
})
|
|
232
232
|
.catch((error) => {
|
|
233
|
-
console.error("❌
|
|
233
|
+
console.error("❌ data-sync redaction-bypass test suite failed:", error)
|
|
234
234
|
process.exit(1)
|
|
235
235
|
})
|
|
236
236
|
}
|