Element Call reports full URLs of visited pages to analytics server
Description
Element Call versions 0.5.17–0.19.3 leaked full URLs including encryption passwords to PostHog analytics via configured fields, compromising call confidentiality.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Element Call versions 0.5.17–0.19.3 leaked full URLs including encryption passwords to PostHog analytics via configured fields, compromising call confidentiality.
Vulnerability
Element Call versions 0.5.17 through 0.19.3 (when PostHog analytics is enabled via posthog in config.json or via URL parameters) report analytics data to a configured PostHog server. The fields $initial_person_info, $session_entry_url, and $current_url contain the full URL of the user's visited page, including the URL fragment. This fragment may contain encryption passwords for calls in standalone instances (e.g., https://call.element.io). [1][2]
Exploitation
An attacker would need access to the PostHog analytics data and the encrypted media streams of a call. If a standalone Element Call user creates a call link with a password in the URL fragment (e.g., #password), that password is transmitted as part of the analytics data. The embedded package is not practically affected because encryption keys are distributed via Matrix, not encoded in the URL. [1][2]
Impact
Successful exploitation could allow an attacker who can inspect PostHog analytics and the corresponding encrypted media streams to decrypt call content, compromising confidentiality. The attack does not impact embedded packages (Element Web, Desktop, X iOS, X Android). [1][2]
Mitigation
Patched in Element Call version 0.19.4. Users can opt out of analytics in the Feedback tab and create new call links. Admins can disable PostHog entirely by removing the posthog key from config.json. [1][2][3]
AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: >= 0.5.17, <= 0.19.3
Patches
14669ef3360bePosthog: drop $initial_person_info from outgoing events (#3968)
4 files changed · +513 −45
package.json+1 −1 modified@@ -118,7 +118,7 @@ "pako": "^2.0.4", "postcss": "^8.4.41", "postcss-preset-env": "^10.0.0", - "posthog-js": "1.160.3", + "posthog-js": "1.374.0", "prettier": "^3.0.0", "qrcode": "^1.5.4", "react": "19",
pnpm-lock.yaml+284 −14 modified@@ -259,8 +259,8 @@ importers: specifier: ^10.0.0 version: 10.6.1(postcss@8.5.11) posthog-js: - specifier: 1.160.3 - version: 1.160.3 + specifier: 1.374.0 + version: 1.374.0 prettier: specifier: ^3.0.0 version: 3.8.3 @@ -329,7 +329,7 @@ importers: version: 3.6.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) vitest: specifier: ^4.0.18 - version: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) vitest-axe: specifier: ^1.0.0-pre.3 version: 1.0.0-pre.5(vitest@4.1.5) @@ -1666,6 +1666,78 @@ packages: '@octokit/types@13.10.0': resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.208.0': + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.208.0': + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.208.0': + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.208.0': + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} @@ -1874,6 +1946,42 @@ packages: engines: {node: '>=18'} hasBin: true + '@posthog/core@1.29.3': + resolution: {integrity: sha512-OvJSAzqVfZx+L7D874q56FVRTxOIsFBVB3wSB/Uny+DhmfNRGDi1rpZAruEmQYl9WQlQJb1q6JXGAC+rxVXjPA==} + + '@posthog/types@1.374.0': + resolution: {integrity: sha512-qouREpHIxsBS3Gc6a5gZvg6/ykK+4TJAs4wYTUIH/emH1HQfaaLrWzGoEm+/OPwlNxHzw4tQn9OOyxsmr9NF2g==} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2953,6 +3061,9 @@ packages: '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} @@ -3688,6 +3799,9 @@ packages: core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3910,6 +4024,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.4.5: + resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -5051,6 +5168,9 @@ packages: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -5595,8 +5715,8 @@ packages: resolution: {integrity: sha512-5dDj8+lmvA8XB78SmzGI8NlQoksv7IfutGWeVZxiixHbO+p4LDPT3wuG/D9sM/wrjZZ9I+Siy/e117vbFPxSZg==} engines: {node: ^10 || ^12 || >=14} - posthog-js@1.160.3: - resolution: {integrity: sha512-mGvxOIlWPtdPx8EI0MQ81wNKlnH2K0n4RqwQOl044b34BCKiFVzZ7Hc7geMuZNaRAvCi5/5zyGeWHcAYZQxiMQ==} + posthog-js@1.374.0: + resolution: {integrity: sha512-3M2xsHXU7Hl64KGZjljq13jIKiJ4N7npY1n+1Q7VQmQKdVsoTc9geaeoHprZEZCMXp3b2qbWZEvIYjekUN5lAg==} preact@10.29.1: resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} @@ -5636,6 +5756,10 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + protobufjs@7.5.9: + resolution: {integrity: sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==} + engines: {node: '>=12.0.0'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -5658,6 +5782,9 @@ packages: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + querystring-es3@0.2.1: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} @@ -6676,8 +6803,8 @@ packages: resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} engines: {node: 20 || >=22} - web-vitals@4.2.4: - resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + web-vitals@5.2.0: + resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==} webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8331,6 +8458,82 @@ snapshots: dependencies: '@octokit/openapi-types': 24.2.0 + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.1) + protobufjs: 7.5.9 + + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/semantic-conventions@1.41.1': {} + '@oxc-project/types@0.127.0': {} '@oxc-resolver/binding-android-arm-eabi@11.19.1': @@ -8466,6 +8669,34 @@ snapshots: dependencies: playwright: 1.59.1 + '@posthog/core@1.29.3': + dependencies: + '@posthog/types': 1.374.0 + + '@posthog/types@1.374.0': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -9439,6 +9670,9 @@ snapshots: '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': + optional: true + '@types/uuid@10.0.0': {} '@types/yargs-parser@21.0.3': {} @@ -9748,7 +9982,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) '@vitest/expect@3.2.4': dependencies: @@ -10334,6 +10568,8 @@ snapshots: dependencies: browserslist: 4.28.2 + core-js@3.49.0: {} + core-util-is@1.0.3: {} cosmiconfig@8.3.6(typescript@5.9.3): @@ -10566,6 +10802,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.4.5: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -11938,6 +12178,8 @@ snapshots: loglevel@1.9.2: {} + long@5.3.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -12609,11 +12851,21 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - posthog-js@1.160.3: - dependencies: + posthog-js@1.374.0: + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + '@posthog/core': 1.29.3 + '@posthog/types': 1.374.0 + core-js: 3.49.0 + dompurify: 3.4.5 fflate: 0.4.8 preact: 10.29.1 - web-vitals: 4.2.4 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.2.0 preact@10.29.1: {} @@ -12646,6 +12898,21 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + protobufjs@7.5.9: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 24.12.2 + long: 5.3.2 + proxy-from-env@1.1.0: {} public-encrypt@4.0.3: @@ -12671,6 +12938,8 @@ snapshots: dependencies: side-channel: 1.1.0 + query-selector-shadow-dom@1.0.1: {} + querystring-es3@0.2.1: {} queue-microtask@1.2.3: {} @@ -13779,9 +14048,9 @@ snapshots: axe-core: 4.11.3 chalk: 5.6.2 lodash-es: 4.18.1 - vitest: 4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) - vitest@4.1.5(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.5 '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3)) @@ -13804,6 +14073,7 @@ snapshots: vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.99.0)(terser@5.46.1)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 24.12.2 '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) jsdom: 26.1.0 @@ -13827,7 +14097,7 @@ snapshots: walk-up-path@4.0.0: {} - web-vitals@4.2.4: {} + web-vitals@5.2.0: {} webidl-conversions@3.0.1: {}
src/analytics/PosthogAnalytics.test.ts+156 −1 modified@@ -14,8 +14,13 @@ import { beforeAll, afterAll, } from "vitest"; +import posthog, { type CaptureResult } from "posthog-js"; -import { PosthogAnalytics } from "./PosthogAnalytics"; +import { + Anonymity, + santizeSensitiveData, + PosthogAnalytics, +} from "./PosthogAnalytics"; import { mockConfig } from "../utils/test"; describe("PosthogAnalytics", () => { @@ -88,4 +93,154 @@ describe("PosthogAnalytics", () => { expect(PosthogAnalytics.instance.isEnabled()).toBe(true); }); }); + + describe("applyPrivacyFilters", () => { + const makeEvent = (properties: Record<string, unknown>): CaptureResult => + ({ event: "anyEvent", properties }) as unknown as CaptureResult; + + it("drops $initial_person_info regardless of anonymity", () => { + const out = santizeSensitiveData( + makeEvent({ + $current_url: "https://call.example.com/some/private/path", + $initial_person_info: { + r: "https://example.com/referrer", + u: "https://call.example.com/some/private/path", + }, + }), + Anonymity.Pseudonymous, + ); + expect(out?.properties).not.toHaveProperty("$initial_person_info"); + }); + + it("strips hash from $current_url", () => { + const out = santizeSensitiveData( + makeEvent({ $current_url: "https://call.example.com/#/x/y/z" }), + Anonymity.Pseudonymous, + ); + expect(out?.properties["$current_url"]).not.toContain("/x/y/z"); + }); + + it("nulls referrer and device fields when anonymous", () => { + const out = santizeSensitiveData( + makeEvent({ + $current_url: "https://x/y", + $referrer: "https://leaky", + $initial_referrer: "https://leaky-too", + $device_id: "uuid", + }), + Anonymity.Anonymous, + ); + expect(out?.properties["$referrer"]).toBeUndefined(); + expect(out?.properties["$initial_referrer"]).toBeUndefined(); + expect(out?.properties["$device_id"]).toBeUndefined(); + }); + + it("passes null events through unchanged", () => { + expect(santizeSensitiveData(null, Anonymity.Pseudonymous)).toBeNull(); + }); + + it("strips URL fields nested inside $set_once", () => { + const secretUrl = + "https://call.example.com/room/#/?password=hunter2&roomId=abc"; + const out = santizeSensitiveData( + makeEvent({ + $current_url: "https://call.example.com/x", + $set_once: { + $current_url: secretUrl, + $initial_current_url: secretUrl, + $session_entry_url: secretUrl, + $initial_person_info: { r: "x", u: secretUrl }, + }, + }), + Anonymity.Pseudonymous, + ); + + const setOnce = out?.properties["$set_once"] as Record<string, unknown>; + expect(setOnce["$current_url"]).not.toContain("password"); + expect(setOnce["$initial_current_url"]).not.toContain("password"); + expect(setOnce).not.toHaveProperty("$session_entry_url"); + expect(setOnce).not.toHaveProperty("$initial_person_info"); + }); + + it("strips URL fields nested inside $set", () => { + const secretUrl = + "https://call.example.com/room/#/?password=hunter2&roomId=abc"; + const out = santizeSensitiveData( + makeEvent({ + $current_url: "https://call.example.com/x", + $set: { + $current_url: secretUrl, + $session_entry_url: secretUrl, + }, + }), + Anonymity.Pseudonymous, + ); + + const set = out?.properties["$set"] as Record<string, unknown>; + expect(set["$current_url"]).not.toContain("password"); + expect(set).not.toHaveProperty("$session_entry_url"); + }); + + it("nulls referrer fields inside $set_once when anonymous", () => { + const out = santizeSensitiveData( + makeEvent({ + $current_url: "https://x/y", + $set_once: { + $initial_referrer: "https://leaky", + $initial_referring_domain: "leaky", + }, + }), + Anonymity.Anonymous, + ); + + const setOnce = out?.properties["$set_once"] as Record<string, unknown>; + expect(setOnce["$initial_referrer"]).toBeUndefined(); + expect(setOnce["$initial_referring_domain"]).toBeUndefined(); + }); + }); + + // Verifies that applyPrivacyFilters is actually wired into posthog.init via + // the before_send hook — guards against typos in the option name or future + // posthog-js bumps renaming/removing the hook. The filter logic itself is + // covered by the applyPrivacyFilters block above. + describe("posthog.init wiring", () => { + beforeAll(() => { + vi.stubEnv("VITE_PACKAGE", "full"); + }); + + beforeEach(() => { + mockConfig({ + posthog: { + api_host: "https://api.example.com.localhost", + api_key: "api_key", + }, + }); + PosthogAnalytics.resetInstance(); + }); + + afterAll(() => { + vi.unstubAllEnvs(); + }); + + it("passes events through the privacy filter via before_send", () => { + const initSpy = vi.spyOn(posthog, "init"); + expect(PosthogAnalytics.instance.isEnabled()).toBe(true); + + const beforeSend = initSpy.mock.calls[0][1]?.before_send; + expect(beforeSend).toBeInstanceOf(Function); + + const event = { + event: "anyEvent", + properties: { + $current_url: "https://call.example.com/x/y", + $initial_person_info: { r: "x" }, + }, + } as unknown as CaptureResult; + + const out = (beforeSend as (e: CaptureResult) => CaptureResult | null)( + event, + ); + expect(out?.properties).not.toHaveProperty("$initial_person_info"); + }); + }); });
src/analytics/PosthogAnalytics.ts+72 −29 modified@@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details. import posthog, { type CaptureOptions, + type CaptureResult, type PostHog, type Properties, } from "posthog-js"; @@ -64,6 +65,73 @@ export enum RegistrationType { Registered, } +// Sanitize URL / referrer / device fields on a single posthog properties bag. +// Applied to event.properties and to the person-profile bags ($set / $set_once), +// since posthog mirrors the same URL fields into those. +function stripSensitiveFields( + obj: Properties | undefined, + anonymity: Anonymity, +): void { + if (!obj) return; + + if (anonymity === Anonymity.Anonymous) { + // drop referrer information for anonymous users + delete obj["$referrer"]; + delete obj["$referring_domain"]; + delete obj["$initial_referrer"]; + delete obj["$initial_referring_domain"]; + + // drop device ID, which is a UUID persisted in local storage + delete obj["$device_id"]; + } + + // the url leaks a lot of private data like the call name or the user + // (room password / room ID can land in the hash/query). Strip down to + // scheme + host so we still get host-level insights (develop / main / sfu). + for (const key of ["$current_url", "$initial_current_url"]) { + if (typeof obj[key] === "string") { + try { + const url = new URL(obj[key]); + obj[key] = url.protocol + "//" + url.hostname + url.pathname; + } catch { + obj[key] = null; + } + } + } + + // $session_entry_url carries the full untrimmed URL; $initial_person_info + // bundles initial referrer + URL into a nested object that bypasses the + // per-key strips above. Drop both. + delete obj["$session_entry_url"]; + delete obj["$initial_person_info"]; +} + +/** + * Strip PII from posthog's built-in properties (URL, referrer fields, + * device ID, $initial_person_info, $session_entry_url) before events leave + * the client. Also applied to the person-profile bags ($set / $set_once), + * which mirror the same URL fields. + * See src/utils/event-utils.ts in posthog-js (getEventProperties, getPersonInfo) + * for the list of properties posthog sets automatically. + */ +export function santizeSensitiveData( + event: CaptureResult | null, + anonymity: Anonymity, +): CaptureResult | null { + if (event === null) return null; + + stripSensitiveFields(event.properties, anonymity); + // posthog can stash person-profile updates either at the top level + // of CaptureResult or nested inside properties depending on the pipeline + // stage; clean both spots so nothing slips through. + stripSensitiveFields(event.$set, anonymity); + stripSensitiveFields(event.$set_once, anonymity); + stripSensitiveFields(event.properties["$set"], anonymity); + stripSensitiveFields(event.properties["$set_once"], anonymity); + + return event; +} + interface PlatformProperties { appVersion: string; matrixBackend: "embedded" | "jssdk"; @@ -128,13 +196,16 @@ export class PosthogAnalytics { } if (apiKey && apiHost) { + const beforeSend = (event: CaptureResult | null): CaptureResult | null => + santizeSensitiveData(event, this.anonymity); this.posthog.init(apiKey, { api_host: apiHost, autocapture: false, mask_all_text: true, mask_all_element_attributes: true, + mask_personal_data_properties: true, capture_pageview: false, - sanitize_properties: this.sanitizeProperties, + before_send: beforeSend, respect_dnt: true, advanced_disable_decide: true, }); @@ -147,34 +218,6 @@ export class PosthogAnalytics { } } - private sanitizeProperties = ( - properties: Properties, - _eventName: string, - ): Properties => { - // Callback from posthog to sanitize properties before sending them to the server. - // Here we sanitize posthog's built in properties which leak PII e.g. url reporting. - // See utils.js _.info.properties in posthog-js. - - if (this.anonymity == Anonymity.Anonymous) { - // drop referrer information for anonymous users - properties["$referrer"] = null; - properties["$referring_domain"] = null; - properties["$initial_referrer"] = null; - properties["$initial_referring_domain"] = null; - - // drop device ID, which is a UUID persisted in local storage - properties["$device_id"] = null; - } - // the url leaks a lot of private data like the call name or the user. - // Its stripped down to the bare minimum to only give insights about the host (develop, main or sfu) - properties["$current_url"] = (properties["$current_url"] as string) - .split("/") - .slice(0, 3) - .join(""); - - return properties; - }; - private registerSuperProperties(properties: Properties): void { if (this.enabled) { this.posthog.register(properties);
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
3News mentions
0No linked articles in our index yet.