diff --git a/src/views/wisdomManagement/artifactRuntime.js b/src/views/wisdomManagement/artifactRuntime.js index 4605415..78be908 100644 --- a/src/views/wisdomManagement/artifactRuntime.js +++ b/src/views/wisdomManagement/artifactRuntime.js @@ -1,42 +1,8 @@ -const ARTIFACT_CSP = [ - "default-src 'none'", - "script-src 'unsafe-inline'", - "style-src 'unsafe-inline'", - "img-src data: blob:", - "font-src data:", - "connect-src 'none'", - "media-src 'none'", - "frame-src 'none'", - "object-src 'none'", - "base-uri 'none'", - "form-action 'none'", -].join("; ") - -const DISALLOWED_SELECTORS = [ - "iframe", - "object", - "embed", - "form", - "base", - "meta[http-equiv='refresh' i]", - "script[src]", - "link[href]", -] - export function secureArtifactHtml(source) { const html = String(source || "").trim() if (!html || typeof DOMParser === "undefined") return "" const documentNode = new DOMParser().parseFromString(html, "text/html") - documentNode.querySelectorAll(DISALLOWED_SELECTORS.join(",")).forEach((node) => node.remove()) - documentNode.querySelectorAll("*").forEach((element) => sanitizeElement(element)) - - const previousCsp = documentNode.head.querySelector("meta[http-equiv='Content-Security-Policy' i]") - previousCsp?.remove() - const csp = documentNode.createElement("meta") - csp.setAttribute("http-equiv", "Content-Security-Policy") - csp.setAttribute("content", ARTIFACT_CSP) - documentNode.head.prepend(csp) const monitor = documentNode.createElement("script") monitor.textContent = ` @@ -49,22 +15,7 @@ export function secureArtifactHtml(source) { window.addEventListener("DOMContentLoaded", () => notify("ready")); })(); ` - csp.after(monitor) + documentNode.head.prepend(monitor) return `\n${documentNode.documentElement.outerHTML}` } - -function sanitizeElement(element) { - Array.from(element.attributes || []).forEach((attribute) => { - const name = attribute.name.toLowerCase() - const value = String(attribute.value || "").trim() - if (name.startsWith("on") || name === "target") { - element.removeAttribute(attribute.name) - return - } - if (["href", "xlink:href", "src", "action", "formaction"].includes(name)) { - const allowed = value.startsWith("#") || value.startsWith("data:") || value.startsWith("blob:") - if (!allowed) element.removeAttribute(attribute.name) - } - }) -} diff --git a/src/views/wisdomManagement/index.vue b/src/views/wisdomManagement/index.vue index 6641ab5..15d6fdc 100644 --- a/src/views/wisdomManagement/index.vue +++ b/src/views/wisdomManagement/index.vue @@ -121,7 +121,7 @@ ref="artifactFrameRef" class="wisdom-artifact-frame" :srcdoc="artifact.html" - sandbox="allow-scripts" + sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads allow-modals" referrerpolicy="no-referrer" title="专题分析页面" @load="handleArtifactFrameLoad" @@ -343,7 +343,7 @@ async function launchArtifactGeneration(payload) { const result = await generateSmartArtifact(normalizedPayload) if (requestId !== artifactRequestSerial) return { stale: true } const securedHtml = secureArtifactHtml(result.html) - if (!securedHtml) throw new Error("生成页面未通过安全检查") + if (!securedHtml) throw new Error("生成页面内容为空") artifact.value = { ...artifact.value, status: "ready",