fix: XSS prevention and frontend timer cleanup

- Add DOMPurify to sanitize all v-html content
- Create sanitize.js utility with sanitizeHtml()
- Wrap renderMarkdown output in Step4Report and Step5Interaction
- Guard all setInterval calls with clearInterval to prevent timer leaks
This commit is contained in:
Nyk 2026-03-17 21:24:34 +07:00
parent 985f89f49a
commit af5c1c6841
7 changed files with 1399 additions and 2 deletions

View File

@ -11,6 +11,7 @@
"dependencies": {
"axios": "^1.13.2",
"d3": "^7.9.0",
"dompurify": "^3.3.3",
"vue": "^3.5.24",
"vue-router": "^4.6.3"
},

1384
frontend/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -464,10 +464,12 @@ let statusTimer = null
let detailTimer = null
const startStatusPolling = () => {
if (statusTimer) clearInterval(statusTimer)
statusTimer = setInterval(fetchRunStatus, 2000)
}
const startDetailPolling = () => {
if (detailTimer) clearInterval(detailTimer)
detailTimer = setInterval(fetchRunStatusDetail, 3000)
}

View File

@ -393,6 +393,7 @@
import { ref, computed, watch, onMounted, onUnmounted, nextTick, h, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { getAgentLog, getConsoleLog } from '../api/report'
import { sanitizeHtml } from '../utils/sanitize.js'
const router = useRouter()
@ -1968,7 +1969,7 @@ const renderMarkdown = (content) => {
}
html = tokens.join('')
return html
return sanitizeHtml(html)
}
const getTimelineItemClass = (log, idx, total) => {

View File

@ -413,6 +413,7 @@
<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { chatWithReport, getReport, getAgentLog } from '../api/report'
import { sanitizeHtml } from '../utils/sanitize.js'
import { interviewAgents, getSimulationProfilesRealtime } from '../api/simulation'
const props = defineProps({
@ -635,7 +636,7 @@ const renderMarkdown = (content) => {
}
html = tokens.join('')
return html
return sanitizeHtml(html)
}
// Chat Methods

View File

@ -0,0 +1,6 @@
import DOMPurify from 'dompurify'
export function sanitizeHtml(html) {
if (!html) return ''
return DOMPurify.sanitize(html)
}

View File

@ -292,6 +292,7 @@ const startBuildGraph = async () => {
const startGraphPolling = () => {
addLog('Started polling for graph data...')
fetchGraphData()
if (graphPollTimer) clearInterval(graphPollTimer)
graphPollTimer = setInterval(fetchGraphData, 10000)
}
@ -315,6 +316,7 @@ const fetchGraphData = async () => {
const startPollingTask = (taskId) => {
pollTaskStatus(taskId)
if (pollTimer) clearInterval(pollTimer)
pollTimer = setInterval(() => pollTaskStatus(taskId), 2000)
}