fix: harden auth rendering and parsing
This commit is contained in:
1 parent
65e610d681
commit
b034988dd4
10 files changed
+609
-34
No files matched your search
+5
-1
@@ -1,5 +1,7 @@
|
||||
import "@mariozechner/mini-lit/dist/ThemeToggle.js";
|
||||
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
|
||||
import "./app.css";
|
||||
import { installMarkdownSecurityPatch } from "./markdown-security-patch.js";
|
||||
|
||||
import { html, render, nothing } from "lit";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
@@ -19,6 +21,7 @@ import {
|
||||
void MessageList;
|
||||
void MessageEditor;
|
||||
void StreamingMessageContainer;
|
||||
installMarkdownSecurityPatch();
|
||||
installAssistantMetadataRenderer();
|
||||
import type {
|
||||
ClientMessage,
|
||||
@@ -27,6 +30,7 @@ import type {
|
||||
SerializedAgentState,
|
||||
SessionListItem,
|
||||
} from "../shared/protocol.js";
|
||||
import { sanitizeMarkdownLinks } from "../shared/markdown-security.js";
|
||||
|
||||
function toolResultText(result: ToolResultMessage | undefined): string {
|
||||
return result?.content
|
||||
@@ -234,7 +238,7 @@ function installAssistantMetadataRenderer() {
|
||||
|
||||
for (const chunk of this.message.content) {
|
||||
if (chunk.type === "text" && chunk.text.trim() !== "") {
|
||||
orderedParts.push(html`<markdown-block .content=${chunk.text}></markdown-block>`);
|
||||
orderedParts.push(html`<markdown-block .content=${sanitizeMarkdownLinks(chunk.text)}></markdown-block>`);
|
||||
} else if (chunk.type === "thinking" && chunk.thinking.trim() !== "") {
|
||||
orderedParts.push(html`<thinking-block .content=${chunk.thinking} .isStreaming=${this.isStreaming}></thinking-block>`);
|
||||
} else if (chunk.type === "toolCall" && !this.hideToolCalls) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { isSafeMarkdownLink } from "../shared/markdown-security.js";
|
||||
|
||||
function sanitizeMarkdownElement(root: ParentNode) {
|
||||
for (const anchor of root.querySelectorAll("a[href]")) {
|
||||
const href = anchor.getAttribute("href") || "";
|
||||
if (isSafeMarkdownLink(href)) continue;
|
||||
anchor.removeAttribute("href");
|
||||
anchor.setAttribute("aria-disabled", "true");
|
||||
anchor.setAttribute("title", "Unsafe link blocked");
|
||||
}
|
||||
for (const image of root.querySelectorAll("img[src]")) {
|
||||
const src = image.getAttribute("src") || "";
|
||||
if (isSafeMarkdownLink(src) && !src.trim().toLowerCase().startsWith("mailto:") && !src.trim().toLowerCase().startsWith("tel:")) continue;
|
||||
image.removeAttribute("src");
|
||||
image.setAttribute("title", "Unsafe image source blocked");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mini-lit renders Marked output through unsafeHTML. Sanitize the resulting DOM
|
||||
* after every markdown-block update so inline links, reference links, autolinks,
|
||||
* images, thinking blocks, and artifact previews all use one URL policy.
|
||||
*/
|
||||
export function installMarkdownSecurityPatch() {
|
||||
const MarkdownBlock = customElements.get("markdown-block") as any;
|
||||
if (!MarkdownBlock || MarkdownBlock.prototype.__piWebuiLinkSecurityPatch) return;
|
||||
MarkdownBlock.prototype.__piWebuiLinkSecurityPatch = true;
|
||||
const originalUpdated = MarkdownBlock.prototype.updated;
|
||||
MarkdownBlock.prototype.updated = function (...args: any[]) {
|
||||
if (typeof originalUpdated === "function") originalUpdated.apply(this, args);
|
||||
sanitizeMarkdownElement(this);
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user