CVE-2025-59053
Description
AIRI is a self-hosted, artificial intelligence based Grok Companion. In v0.7.2-beta.2 in the packages/stage-ui/src/components/MarkdownRenderer.vue path, the Markdown content is processed using the useMarkdown composable, and the processed HTML is rendered directly into the DOM using v-html. An attacker creates a card file containing malicious HTML/JavaScript, then simply processes it using the highlightTagToHtml function (which simply replaces template tags without HTML escaping), and then directly renders it using v-html, leading to cross-site scripting (XSS). The project also exposes the Tauri API, which can be called from the frontend. The MCP plugin exposes a command execution interface function in crates/tauri-plugin-mcp/src/lib.rs. This allows arbitrary command execution. connect_server directly passes the user-supplied command and args parameters to Command::new(command).args(args) without any input validation or whitelisting. Thus, the previous XSS exploit could achieve command execution through this interface. v0.7.2-beta.3 fixes the issue.
Affected products
1Patches
13315634903c9fix: sanitize v-html inputs with DOMPurify (#573)
9 files changed · +51 −8
apps/stage-tamagotchi-electron/src/renderer/pages/settings/airi-card/components/CardDetailDialog.vue+3 −1 modified@@ -1,6 +1,8 @@ <script setup lang="ts"> import type { AiriCard } from '@proj-airi/stage-ui/stores/modules/airi-card' +import DOMPurify from 'dompurify' + import { Button } from '@proj-airi/stage-ui/components' import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card' import { storeToRefs } from 'pinia' @@ -84,7 +86,7 @@ function handleActivate() { } function highlightTagToHtml(text: string) { - return text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim() + return DOMPurify.sanitize(text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim()) } // Delete confirmation
apps/stage-tamagotchi/package.json+1 −0 modified@@ -56,6 +56,7 @@ "colorjs.io": "^0.5.2", "culori": "^4.0.2", "date-fns": "^4.1.0", + "dompurify": "^3.2.6", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.5", "jszip": "^3.10.1",
apps/stage-tamagotchi/src/pages/settings/airi-card/components/CardDetailDialog.vue+3 −1 modified@@ -1,6 +1,8 @@ <script setup lang="ts"> import type { AiriCard } from '@proj-airi/stage-ui/stores/modules/airi-card' +import DOMPurify from 'dompurify' + import { Button } from '@proj-airi/stage-ui/components' import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card' import { storeToRefs } from 'pinia' @@ -84,7 +86,7 @@ function handleActivate() { } function highlightTagToHtml(text: string) { - return text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim() + return DOMPurify.sanitize(text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim()) } // Delete confirmation
apps/stage-web/package.json+1 −0 modified@@ -55,6 +55,7 @@ "colorjs.io": "^0.5.2", "culori": "^4.0.2", "date-fns": "^4.1.0", + "dompurify": "^3.2.6", "driver.js": "^1.3.6", "drizzle-kit": "^0.31.4", "drizzle-orm": "^0.44.5",
apps/stage-web/src/pages/settings/airi-card/components/CardDetailDialog.vue+3 −1 modified@@ -1,6 +1,8 @@ <script setup lang="ts"> import type { AiriCard } from '@proj-airi/stage-ui/stores/modules/airi-card' +import DOMPurify from 'dompurify' + import { Button } from '@proj-airi/stage-ui/components' import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card' import { storeToRefs } from 'pinia' @@ -84,7 +86,7 @@ function handleActivate() { } function highlightTagToHtml(text: string) { - return text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim() + return DOMPurify.sanitize(text?.replace(/\{\{(.*?)\}\}/g, '<span class="bg-primary-500/20 inline-block">{{ $1 }}</span>').trim()) } // Delete confirmation
cspell.config.yaml+1 −0 modified@@ -71,6 +71,7 @@ words: - devlogs - directml - dokidoki + - dompurify - dotenvx - DownloadLive2DSDK - dreamlog
packages/stage-ui/package.json+1 −0 modified@@ -93,6 +93,7 @@ "animejs": "^4.1.3", "culori": "^4.0.2", "date-fns": "^4.1.0", + "dompurify": "^3.2.6", "gpuu": "^1.0.4", "jszip": "^3.10.1", "localforage": "^1.10.0",
packages/stage-ui/src/components/MarkdownRenderer.vue+4 −2 modified@@ -1,4 +1,6 @@ <script setup lang="ts"> +import DOMPurify from 'dompurify' + import { onMounted, ref, watch } from 'vue' import { useMarkdown } from '../composables/markdown' @@ -20,11 +22,11 @@ async function processContent() { } try { - processedContent.value = await process(props.content) + processedContent.value = DOMPurify.sanitize(await process(props.content)) } catch (error) { console.warn('Failed to process markdown with syntax highlighting, using fallback:', error) - processedContent.value = processSync(props.content) + processedContent.value = DOMPurify.sanitize(processSync(props.content)) } }
pnpm-lock.yaml+34 −3 modified@@ -284,7 +284,7 @@ importers: version: rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) vite-plugin-vue-devtools: specifier: ^8.0.1 - version: 8.0.1(@nuxt/kit@4.0.3(magicast@0.3.5))(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 8.0.1(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) vite-plugin-vue-layouts: specifier: ^0.11.0 version: 0.11.0(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)) @@ -454,6 +454,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dompurify: + specifier: ^3.2.6 + version: 3.2.6 drizzle-kit: specifier: ^0.31.4 version: 0.31.4 @@ -1164,6 +1167,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dompurify: + specifier: ^3.2.6 + version: 3.2.6 driver.js: specifier: ^1.3.6 version: 1.3.6 @@ -1768,6 +1774,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dompurify: + specifier: ^3.2.6 + version: 3.2.6 gpuu: specifier: ^1.0.4 version: 1.0.4 @@ -2023,7 +2032,7 @@ importers: version: rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) vite-plugin-vue-devtools: specifier: ^8.0.1 - version: 8.0.1(@nuxt/kit@4.0.3(magicast@0.3.5))(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 8.0.1(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) vue-router: specifier: ^4.5.1 version: 4.5.1(vue@3.5.21(typescript@5.9.2)) @@ -2057,7 +2066,7 @@ importers: version: rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) vite-plugin-vue-devtools: specifier: ^8.0.1 - version: 8.0.1(@nuxt/kit@4.0.3(magicast@0.3.5))(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + version: 8.0.1(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) vue-router: specifier: ^4.5.1 version: 4.5.1(vue@3.5.21(typescript@5.9.2)) @@ -9588,6 +9597,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.2.6: + resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -25071,6 +25083,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -33075,6 +33091,21 @@ snapshots: - supports-color - vue + vite-plugin-vue-devtools@8.0.1(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)): + dependencies: + '@vue/devtools-core': 8.0.1(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)) + '@vue/devtools-kit': 8.0.1 + '@vue/devtools-shared': 8.0.1 + execa: 9.6.0 + sirv: 3.0.1 + vite: rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1) + vite-plugin-inspect: 11.3.3(@nuxt/kit@3.18.1(magicast@0.3.5))(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) + vite-plugin-vue-inspector: 5.3.2(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)) + transitivePeerDependencies: + - '@nuxt/kit' + - supports-color + - vue + vite-plugin-vue-inspector@5.3.2(rolldown-vite@7.1.5(@types/node@24.3.0)(esbuild@0.25.9)(jiti@2.5.1)(less@4.4.1)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)): dependencies: '@babel/core': 7.28.3
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.