@tellescope/sdk 1.251.0 → 1.252.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  2. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js +337 -0
  4. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  6. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js +287 -0
  8. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -1
  10. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js +234 -198
  11. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -1
  12. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  13. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  14. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +349 -0
  15. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  16. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  17. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +247 -0
  19. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  20. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  21. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  22. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +278 -0
  23. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  24. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  25. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  26. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +201 -0
  27. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  28. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  29. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  30. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js +148 -0
  31. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  32. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  33. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  34. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js +88 -0
  35. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  36. package/lib/cjs/tests/tests.d.ts.map +1 -1
  37. package/lib/cjs/tests/tests.js +186 -151
  38. package/lib/cjs/tests/tests.js.map +1 -1
  39. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  40. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  41. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js +333 -0
  42. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  43. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  44. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  45. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js +280 -0
  46. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  47. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -1
  48. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js +235 -199
  49. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -1
  50. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  51. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  52. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +345 -0
  53. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  54. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  55. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  56. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +243 -0
  57. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  58. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  59. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  60. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +271 -0
  61. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  62. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  63. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  64. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +194 -0
  65. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  66. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  67. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  68. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js +144 -0
  69. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  70. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  71. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  72. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js +84 -0
  73. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  74. package/lib/esm/tests/tests.d.ts.map +1 -1
  75. package/lib/esm/tests/tests.js +186 -151
  76. package/lib/esm/tests/tests.js.map +1 -1
  77. package/lib/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +10 -10
  79. package/src/tests/api_tests/calendar_event_webhook_template.test.ts +204 -0
  80. package/src/tests/api_tests/enduser_login_rate_limits.test.ts +178 -0
  81. package/src/tests/api_tests/push_forms_to_portal_group_completion.test.ts +113 -88
  82. package/src/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.ts +236 -0
  83. package/src/tests/api_tests/security/F-0005-ai-conversations-rbac.test.ts +154 -0
  84. package/src/tests/api_tests/security/F-0007-invite-user-enumeration.test.ts +198 -0
  85. package/src/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.ts +130 -0
  86. package/src/tests/api_tests/security/F-0013-sanitize-user-html.test.ts +109 -0
  87. package/src/tests/api_tests/security/F-0016-prototype-pollution.test.ts +50 -0
  88. package/src/tests/tests.ts +19 -2
  89. package/test_generated.pdf +0 -0
@@ -0,0 +1,130 @@
1
+ require('source-map-support').install();
2
+
3
+ import axios from "axios"
4
+ import { ObjectId } from 'bson'
5
+ import { Session } from "../../../sdk"
6
+ import {
7
+ async_test,
8
+ log_header,
9
+ } from "@tellescope/testing"
10
+ import { setup_tests } from "../../setup"
11
+
12
+ const host = process.env.API_URL || 'http://localhost:8080' as const
13
+
14
+ const CROSS_ORG_API_KEY = process.env.CROSS_ORG_API_KEY
15
+ const CROSS_ORG_TARGET_BUSINESS_ID = process.env.CROSS_ORG_TARGET_BUSINESS_ID
16
+
17
+ const post = async (path: string, body: any, headers: Record<string, string> = {}) => {
18
+ try {
19
+ const res = await axios.post(`${host}${path}`, body, {
20
+ validateStatus: () => true,
21
+ headers,
22
+ })
23
+ return { status: res.status, data: res.data }
24
+ } catch (err: any) {
25
+ return { status: err?.response?.status, data: err?.response?.data }
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Regression test for F-0008 (security-audit/findings/F-0008-handle-incoming-communication-cross-tenant-enduser-lookup.md).
31
+ *
32
+ * `journeys.handle_incoming_communication` previously used `buildAllQueries({ unrestricted: true, organizationIds: [] }).endusers.findById(enduserId)`,
33
+ * permitting cross-tenant lookup of any enduser by id. The handler then called `handleIncomingCommunication(...)`
34
+ * against the matched enduser, triggering journey progression and automated actions on someone else's tenant.
35
+ *
36
+ * The fix switches to the standard tenant-scoped `DB.endusers.findById(enduserId)` wrapper, which automatically
37
+ * filters by `req.session.businessId`. Cross-tenant lookups now return null → handler returns 404 → no side effect.
38
+ *
39
+ * Note: the same-tenant happy path is already covered by the existing test at
40
+ * `packages/public/sdk/src/tests/tests.ts:7588` ("handle_incoming_communication test for other enduser") — that
41
+ * test creates endusers in the test tenant, sets up journeys, calls handle_incoming_communication, and asserts
42
+ * journey-step cancellation. This file covers the negative cases only.
43
+ *
44
+ * **Negative-only by design**: the test never drives `handleIncomingCommunication` against a cross-tenant
45
+ * enduser — the post-fix code returns 404 before any side effects fire, and the assertion confirms that.
46
+ */
47
+ export const handle_incoming_communication_cross_tenant_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
48
+ log_header("F-0008: handle_incoming_communication cross-tenant rejection")
49
+
50
+ // ====================================================================
51
+ // Assertion 1: nonexistent enduserId returns 404 (baseline; regression
52
+ // guard for the not-found path that any cross-tenant call now lands in).
53
+ // ====================================================================
54
+ const nonexistentId = new ObjectId().toHexString()
55
+ const nonexistentRes = await post(
56
+ '/v1/journeys/handle-incoming-communication',
57
+ { enduserId: nonexistentId },
58
+ { Authorization: `Bearer ${sdk.authToken}` },
59
+ )
60
+ await async_test(
61
+ "F-0008: handle_incoming_communication with nonexistent enduserId returns 404",
62
+ async () => ({ status: nonexistentRes.status }),
63
+ { onResult: r => r.status === 404 },
64
+ )
65
+
66
+ // ====================================================================
67
+ // Assertion 2: cross-tenant enduserId returns 404 (the actual F-0008 fix).
68
+ // Env-gated; skipped when cross-org infra isn't configured.
69
+ // Safe to run post-fix because the tenant-scoped DB returns null for
70
+ // cross-tenant lookups — no handleIncomingCommunication side effect fires.
71
+ // ====================================================================
72
+ if (!(CROSS_ORG_API_KEY && CROSS_ORG_TARGET_BUSINESS_ID)) {
73
+ console.log(" [F-0008] Skipping cross-tenant rejection assertion — CROSS_ORG_* env vars not set")
74
+ return
75
+ }
76
+
77
+ const sdkCrossOrg = new Session({
78
+ host,
79
+ apiKey: CROSS_ORG_API_KEY,
80
+ headers: { 'x-tellescope-organization': CROSS_ORG_TARGET_BUSINESS_ID },
81
+ })
82
+
83
+ // Create a sentinel enduser in the cross-org tenant. Use a clearly-test email
84
+ // so any accidental side-effect routing is obvious in logs.
85
+ const ts = Date.now()
86
+ const crossEnduser = await sdkCrossOrg.api.endusers.createOne({
87
+ fname: 'F0008CrossTenant', lname: 'Sentinel',
88
+ email: `f0008-cross-${ts}@tellescope.com`,
89
+ } as any)
90
+
91
+ try {
92
+ const crossRes = await post(
93
+ '/v1/journeys/handle-incoming-communication',
94
+ { enduserId: crossEnduser.id },
95
+ { Authorization: `Bearer ${sdk.authToken}` },
96
+ )
97
+
98
+ await async_test(
99
+ "F-0008: handle_incoming_communication with cross-tenant enduserId returns 404 (no side effect)",
100
+ async () => ({
101
+ status: crossRes.status,
102
+ message: crossRes.data?.message ?? null,
103
+ }),
104
+ { onResult: r => r.status === 404 },
105
+ )
106
+ } finally {
107
+ try { await sdkCrossOrg.api.endusers.deleteOne(crossEnduser.id) } catch {}
108
+ }
109
+ }
110
+
111
+ if (require.main === module) {
112
+ console.log(`🌐 Using API URL: ${host}`)
113
+ const sdk = new Session({ host })
114
+ const sdkNonAdmin = new Session({ host })
115
+
116
+ const runTests = async () => {
117
+ await setup_tests(sdk, sdkNonAdmin)
118
+ await handle_incoming_communication_cross_tenant_tests({ sdk, sdkNonAdmin })
119
+ }
120
+
121
+ runTests()
122
+ .then(() => {
123
+ console.log("✅ F-0008 handle_incoming_communication cross-tenant test suite completed successfully")
124
+ process.exit(0)
125
+ })
126
+ .catch((error) => {
127
+ console.error("❌ F-0008 handle_incoming_communication cross-tenant test suite failed:", error)
128
+ process.exit(1)
129
+ })
130
+ }
@@ -0,0 +1,109 @@
1
+ import { sanitize_user_html } from "@tellescope/utilities"
2
+
3
+ // Regression test for F-0013 / F-0014 (pattern 06 — XSS via dangerouslySetInnerHTML).
4
+ // sanitize_user_html is the canonical render-time sanitizer that replaced remove_script_tags
5
+ // at every dangerouslySetInnerHTML sink. This asserts it neutralizes XSS vectors (incl. encoded /
6
+ // whitespace / mixed-case / iframe-srcdoc bypass variants) while preserving legitimate
7
+ // customization HTML (tables, headings, lists, links, images, inline styles).
8
+ //
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/F-0013-sanitize-user-html.test.js
11
+
12
+ const fail = (msg: string) => { throw new Error(msg) }
13
+
14
+ const has_no_executable_vector = (out: string) => {
15
+ const o = out.toLowerCase()
16
+ // A handler smuggled into an attribute VALUE (e.g. title="&lt;img onerror=...&gt;") is inert
17
+ // text — strip quoted values before checking for *live* on*= attributes to avoid false positives.
18
+ const withoutValues = o.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''")
19
+ return !/\son[a-z]+\s*=/.test(withoutValues) // no live on*= event-handler attribute
20
+ && !o.includes('javascript:') // dropped schemes never appear in safe output
21
+ && !o.includes('vbscript:')
22
+ && !o.includes('<script') // literal dangerous tags (encoded &lt;script is fine)
23
+ && !o.includes('<iframe')
24
+ && !o.includes('<svg')
25
+ && !o.includes('<math')
26
+ && !o.includes('<object')
27
+ && !o.includes('<embed')
28
+ && !o.includes('<form')
29
+ && !o.includes('<noscript')
30
+ && !o.includes('<template')
31
+ }
32
+
33
+ export const sanitize_user_html_xss_tests = async () => {
34
+ console.log("Running F-0013/F-0014 sanitize_user_html XSS regression tests")
35
+
36
+ const xssPayloads: [string, string][] = [
37
+ ['img onerror', `<img src=x onerror="alert(document.domain)">`],
38
+ ['svg onload', `<svg onload="alert(1)"></svg>`],
39
+ ['svg animate onbegin', `<svg><animate onbegin="alert(1)" attributeName="x" dur="1s"></svg>`],
40
+ ['details ontoggle', `<details open ontoggle="alert(1)"></details>`],
41
+ ['input onfocus autofocus', `<input autofocus onfocus="alert(1)">`],
42
+ ['body onpageshow', `<body onpageshow="alert(1)">`],
43
+ ['a javascript scheme', `<a href="javascript:alert(1)">x</a>`],
44
+ ['a javascript entity-encoded', `<a href="jav&#x09;ascript:alert(1)">x</a>`],
45
+ ['iframe javascript src', `<iframe src="javascript:alert(1)"></iframe>`],
46
+ ['iframe srcdoc nested', `<iframe srcdoc="<img src=x onerror=alert(1)>"></iframe>`],
47
+ ['script tag', `<script>alert(1)</script>`],
48
+ ['onerror newline before =', `<img src=x onerror\n="alert(1)">`],
49
+ ['onerror mixed case', `<IMG SRC=x OnErRoR="alert(1)">`],
50
+ ['marquee onstart', `<marquee onstart="alert(1)">x</marquee>`],
51
+ // mutation / namespace confusion — svg/math/noscript/template must be stripped
52
+ ['mathml mglyph style mxss', `<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>`],
53
+ ['svg foreignObject', `<svg><foreignObject><img src=x onerror=alert(1)></foreignObject></svg>`],
54
+ ['noscript context confusion', `<noscript><p title="</noscript><img src=x onerror=alert(1)>">`],
55
+ ['template content', `<template><img src=x onerror=alert(1)></template>`],
56
+ // comment / CDATA confusion
57
+ ['comment confusion', `<!--><img src=x onerror=alert(1)>-->`],
58
+ ['cdata confusion', `<![CDATA[<img src=x onerror=alert(1)>]]>`],
59
+ // markup smuggled inside an attribute value must stay inert
60
+ ['markup inside attr value', `<img src="x" alt="<script>alert(1)</script>">`],
61
+ // protocol obfuscation
62
+ ['vbscript scheme', `<a href="vbscript:msgbox(1)">x</a>`],
63
+ ['data text/html href', `<a href="data:text/html,<script>alert(1)</script>">x</a>`],
64
+ ['javascript decimal entity', `<a href="&#74;avascript:alert(1)">x</a>`],
65
+ ['javascript newline entity', `<a href="jav&#x0A;ascript:alert(1)">x</a>`],
66
+ ]
67
+ for (const [name, payload] of xssPayloads) {
68
+ const out = sanitize_user_html(payload)
69
+ if (!has_no_executable_vector(out)) fail(`XSS not neutralized [${name}] -> ${out}`)
70
+ }
71
+
72
+ // DOM clobbering: caller-controlled id/name must be stripped
73
+ const clobber = sanitize_user_html(`<a id="x" name="getElementById">link</a><img name="y">`)
74
+ if (/\b(id|name)\s*=/.test(clobber)) fail(`id/name not stripped (DOM clobbering): ${clobber}`)
75
+
76
+ // legitimate customization HTML must survive
77
+ const heading = sanitize_user_html(`<h1>Welcome</h1><h3 style="color:#333">Sub</h3>`)
78
+ if (!(heading.includes('<h1>') && heading.includes('<h3') && heading.toLowerCase().includes('color'))) fail(`headings/style stripped: ${heading}`)
79
+
80
+ const table = sanitize_user_html(`<table><thead><tr><th>H</th></tr></thead><tbody><tr><td style="padding:4px" colspan="2">cell</td></tr></tbody></table>`)
81
+ if (!(table.includes('<table') && table.includes('<td') && table.includes('colspan'))) fail(`table stripped: ${table}`)
82
+
83
+ const list = sanitize_user_html(`<ul><li>a</li></ul><ol start="3"><li>c</li></ol>`)
84
+ if (!(list.includes('<ul') && list.includes('<li') && list.includes('<ol'))) fail(`list stripped: ${list}`)
85
+
86
+ const link = sanitize_user_html(`<a href="https://example.com">link</a>`)
87
+ if (!link.includes('href="https://example.com"')) fail(`safe link stripped: ${link}`)
88
+ if (!link.toLowerCase().includes('noopener')) fail(`external link not hardened: ${link}`)
89
+
90
+ const img = sanitize_user_html(`<img src="https://cdn.example.com/a.png" alt="pic" width="200">`)
91
+ if (!(img.includes('src="https://cdn.example.com/a.png"') && img.includes('alt="pic"'))) fail(`http image stripped: ${img}`)
92
+
93
+ const dataimg = sanitize_user_html(`<img src="data:image/png;base64,iVBORw0KGgo=">`)
94
+ if (!dataimg.includes('data:image/png')) fail(`data: image stripped: ${dataimg}`)
95
+
96
+ const fmt = sanitize_user_html(`<p><strong>b</strong> <em>i</em> <span style="font-size:14px">s</span></p><blockquote>q</blockquote>`)
97
+ if (!(fmt.includes('<strong>') && fmt.includes('<span') && fmt.toLowerCase().includes('font-size'))) fail(`formatting stripped: ${fmt}`)
98
+
99
+ const mixed = sanitize_user_html(`<p>Hello <b>name</b></p><img src=x onerror="steal()">`)
100
+ if (!(mixed.includes('<b>name</b>') && !/\son[a-z]+\s*=/.test(mixed.toLowerCase()))) fail(`mixed content not handled: ${mixed}`)
101
+
102
+ console.log("✅ F-0013/F-0014 sanitize_user_html XSS regression tests passed")
103
+ }
104
+
105
+ if (require.main === module) {
106
+ sanitize_user_html_xss_tests()
107
+ .then(() => { console.log("✅ suite completed"); process.exit(0) })
108
+ .catch((err) => { console.error("❌ suite failed:", err); process.exit(1) })
109
+ }
@@ -0,0 +1,50 @@
1
+ import { add_value_for_dotted_key } from "@tellescope/utilities"
2
+
3
+ // Regression test for F-0016 (pattern 17 — prototype pollution).
4
+ // add_value_for_dotted_key must NOT write through __proto__/constructor/prototype path segments
5
+ // (which would pollute Object.prototype process-wide), while still performing legitimate dotted assignment.
6
+ //
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/F-0016-prototype-pollution.test.js
9
+
10
+ const fail = (msg: string) => { throw new Error(msg) }
11
+
12
+ export const prototype_pollution_tests = async () => {
13
+ console.log("Running F-0016 prototype-pollution regression tests")
14
+
15
+ // 1. __proto__ path must not pollute Object.prototype
16
+ add_value_for_dotted_key({ insurance: {} } as any, 'insurance.__proto__.__pp_a__', 'polluted')
17
+ const leakedA = ({} as any).__pp_a__
18
+ delete (Object.prototype as any).__pp_a__ // clean up regardless, so a failure here can't contaminate the rest of the suite
19
+ if (leakedA !== undefined) fail('Object.prototype polluted via __proto__ path')
20
+
21
+ // 2. constructor.prototype path must not pollute
22
+ add_value_for_dotted_key({ insurance: {} } as any, 'insurance.constructor.prototype.__pp_b__', 'polluted')
23
+ const leakedB = ({} as any).__pp_b__
24
+ delete (Object.prototype as any).__pp_b__
25
+ if (leakedB !== undefined) fail('Object.prototype polluted via constructor.prototype path')
26
+
27
+ // 3. a leading __proto__ segment must not pollute either
28
+ add_value_for_dotted_key({} as any, '__proto__.__pp_c__', 'polluted')
29
+ const leakedC = ({} as any).__pp_c__
30
+ delete (Object.prototype as any).__pp_c__
31
+ if (leakedC !== undefined) fail('Object.prototype polluted via leading __proto__ segment')
32
+
33
+ // 4. legitimate dotted assignment still works (existing intermediate objects)
34
+ const obj = { a: { b: {} } } as any
35
+ add_value_for_dotted_key(obj, 'a.b.c', 42)
36
+ if (obj.a.b.c !== 42) fail('legitimate dotted assignment broke')
37
+
38
+ // 5. single-key assignment still works
39
+ const flat = {} as any
40
+ add_value_for_dotted_key(flat, 'name', 'ok')
41
+ if (flat.name !== 'ok') fail('single-key assignment broke')
42
+
43
+ console.log("✅ F-0016 prototype-pollution regression tests passed")
44
+ }
45
+
46
+ if (require.main === module) {
47
+ prototype_pollution_tests()
48
+ .then(() => { console.log("✅ suite completed"); process.exit(0) })
49
+ .catch((err) => { console.error("❌ suite failed:", err); process.exit(1) })
50
+ }
@@ -76,6 +76,7 @@ import {
76
76
  import fs from "fs"
77
77
  import { load_inbox_data_tests } from "./api_tests/load_inbox_data.test";
78
78
  import { enduser_login_tests } from "./api_tests/enduser_login.test";
79
+ import { enduser_login_rate_limits_tests } from "./api_tests/enduser_login_rate_limits.test";
79
80
  import { eom_procedure_codes_tests } from "./api_tests/eom_procedure_codes.test";
80
81
  import { cross_org_api_key_tests } from "./api_tests/cross_org_api_key.test";
81
82
  import { custom_dashboards_tests } from "./api_tests/custom_dashboards.test";
@@ -88,6 +89,12 @@ import { custom_aggregation_tests } from "./api_tests/custom_aggregation.test";
88
89
  import { chats_analytics_tests } from "./api_tests/chats_analytics.test";
89
90
  import { no_access_permission_checks_tests } from "./api_tests/no_access_permission_checks.test";
90
91
  import { field_redaction_tests } from "./api_tests/field_redaction.test";
92
+ import { data_sync_redaction_bypass_tests } from "./api_tests/security/F-0001-data-sync-redaction-bypass.test";
93
+ import { ai_conversations_rbac_tests } from "./api_tests/security/F-0005-ai-conversations-rbac.test";
94
+ import { invite_user_enumeration_tests } from "./api_tests/security/F-0007-invite-user-enumeration.test";
95
+ import { handle_incoming_communication_cross_tenant_tests } from "./api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test";
96
+ import { sanitize_user_html_xss_tests } from "./api_tests/security/F-0013-sanitize-user-html.test";
97
+ import { prototype_pollution_tests } from "./api_tests/security/F-0016-prototype-pollution.test";
91
98
  import { bulk_assignment_tests } from "./api_tests/bulk_assignment.test";
92
99
  import { managed_content_enduser_access_tests } from "./api_tests/managed_content_enduser_access.test";
93
100
  import { managed_content_file_access_tests } from "./api_tests/managed_content_file_access.test";
@@ -109,6 +116,7 @@ import { set_fields_order_templates_tests } from "./api_tests/set_fields_order_t
109
116
  import { date_string_validation_tests } from "./api_tests/date_string_validation.test";
110
117
  import { enduser_session_invalidation_tests } from "./api_tests/enduser_session_invalidation.test";
111
118
  import { enduser_cross_access_isolation_tests } from "./api_tests/enduser_cross_access_isolation.test";
119
+ import { calendar_event_webhook_template_tests } from "./api_tests/calendar_event_webhook_template.test";
112
120
 
113
121
  const UniquenessViolationMessage = 'Uniqueness Violation'
114
122
 
@@ -5092,6 +5100,7 @@ const trigger_events_api_tests = async () => {
5092
5100
  const automation_trigger_tests = async () => {
5093
5101
  log_header("Automation Trigger Tests")
5094
5102
 
5103
+ await push_forms_to_portal_group_completion_tests({ sdk, sdkNonAdmin })
5095
5104
  await order_status_equals_tests()
5096
5105
  await set_fields_order_templates_tests({ sdk, sdkNonAdmin })
5097
5106
  await medication_added_trigger_tests({ sdk, sdkNonAdmin })
@@ -5102,7 +5111,6 @@ const automation_trigger_tests = async () => {
5102
5111
  await form_response_set_fields_trigger_tests()
5103
5112
  await form_response_set_fields_journey_tests()
5104
5113
  await appointment_completed_trigger_tests({ sdk, sdkNonAdmin })
5105
- await push_forms_to_portal_group_completion_tests({ sdk, sdkNonAdmin })
5106
5114
  await trigger_events_api_tests()
5107
5115
  await fields_changed_tests()
5108
5116
  await field_equals_trigger_tests()
@@ -14321,10 +14329,19 @@ const ip_address_form_tests = async () => {
14321
14329
  await replace_form_field_template_values_tests()
14322
14330
  await mfa_tests()
14323
14331
  await setup_tests(sdk, sdkNonAdmin)
14332
+ await invite_user_enumeration_tests({ sdk, sdkNonAdmin })
14333
+ await handle_incoming_communication_cross_tenant_tests({ sdk, sdkNonAdmin })
14334
+ await calendar_event_webhook_template_tests({ sdk, sdkNonAdmin })
14335
+ await outbound_chat_sent_trigger_tests({ sdk })
14336
+ await enduser_login_rate_limits_tests({ sdk, sdkNonAdmin })
14337
+ await data_sync_redaction_bypass_tests({ sdk, sdkNonAdmin })
14338
+ await ai_conversations_rbac_tests({ sdk, sdkNonAdmin })
14339
+ await sanitize_user_html_xss_tests()
14340
+ await prototype_pollution_tests()
14341
+ await automation_trigger_tests()
14324
14342
  await account_switcher_tests({ sdk, sdkNonAdmin })
14325
14343
  await enduser_login_tests({ sdk, sdkNonAdmin })
14326
14344
  await outbound_chat_sent_trigger_tests({ sdk })
14327
- await automation_trigger_tests()
14328
14345
  await enduser_cross_access_isolation_tests({ sdk, sdkNonAdmin })
14329
14346
  await eom_procedure_codes_tests({ sdk, sdkNonAdmin })
14330
14347
  await cross_org_api_key_tests({ sdk, sdkNonAdmin })
Binary file