fix(admin): sanitize the richeditor read-only display with DOMPurify (T-10-16)

- displayHtml uses a dedicated DOMPurify 3.4.16 instance: EDITOR_TAGS, href
  only on <a> through isAllowedLink, DROPPED elements go with their content
- string in, string out: parsed into DOMPurify's inert document, nothing
  adopted into the live document before binding
- fails closed (empty string) unless isSupported and a fixed canary hold
- Vitest browser mode in Playwright Chromium (npm run test:browser) covers
  the clobbering vectors, off-origin form, iframe, meta refresh, links and
  the inert parse; happy-dom keeps engine-independent assertions
This commit is contained in:
2026-10-12 20:35:57 +02:00
parent fba15c9378
commit c1d767f0f8
9 changed files with 522 additions and 45 deletions

View File

@@ -10,6 +10,7 @@
"build": "vue-tsc --noEmit && vite build",
"typecheck": "vue-tsc --noEmit",
"test": "vitest run",
"test:browser": "vitest run --config vitest.browser.config.ts",
"gen:api": "openapi-typescript openapi/admin.json -o src/api/schema.d.ts"
},
"dependencies": {
@@ -20,6 +21,7 @@
"@tiptap/core": "3.31.4",
"@tiptap/pm": "3.31.4",
"@tiptap/starter-kit": "3.31.4",
"dompurify": "3.4.16",
"openapi-fetch": "0.17.0",
"reka-ui": "2.9.10",
"vue": "3.5.35",
@@ -28,9 +30,11 @@
"devDependencies": {
"@tailwindcss/vite": "4.3.0",
"@vitejs/plugin-vue": "6.0.8",
"@vitest/browser": "3.2.7",
"@vue/test-utils": "2.4.11",
"happy-dom": "20.11.6",
"openapi-typescript": "7.13.0",
"playwright": "1.60.0",
"tailwindcss": "4.3.0",
"typescript": "5.9.3",
"vite": "7.3.5",