mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-04-28 19:51:11 +00:00
sanitize plugin markdown rendering with shared helper
Add a shared safe markdown pipeline for plugin READMEs and docs. - vendor DOMPurify and introduce a shared safe-markdown helper - centralize GitHub README link/image rebasing, including repo routes like `releases` - sanitize rendered HTML before all plugin-related x-html sinks - apply the shared renderer to Plugin Hub README, installed plugin README, and markdown modal docs - preserve target/rel handling for external links
This commit is contained in:
parent
f577dad6bf
commit
c2e14b6cd1
7 changed files with 1617 additions and 119 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import { createStore } from "/js/AlpineStore.js";
|
||||
import * as api from "/js/api.js";
|
||||
import { addBlankTargetsToLinks } from "/js/messages.js";
|
||||
import { openModal } from "/js/modals.js";
|
||||
import { marked } from "/vendor/marked/marked.esm.js";
|
||||
import { renderSafeMarkdown } from "/js/safe-markdown.js";
|
||||
import { toastFrontendSuccess, toastFrontendError } from "/components/notifications/notification-store.js";
|
||||
import { showConfirmDialog } from "/js/confirmDialog.js";
|
||||
import { store as imageViewerStore } from "/components/modals/image-viewer/image-viewer-store.js";
|
||||
|
|
@ -80,90 +79,6 @@ const model = {
|
|||
return url.replace("https://github.com/", "https://raw.githubusercontent.com/");
|
||||
},
|
||||
|
||||
_rebaseReadmeLinks(html, githubUrl, branch) {
|
||||
if (!html || typeof html !== "string" || !githubUrl || !branch) return html;
|
||||
|
||||
let repoUrl;
|
||||
try {
|
||||
repoUrl = new URL(githubUrl.trim().replace(/\.git$/i, ""));
|
||||
} catch {
|
||||
return html;
|
||||
}
|
||||
|
||||
if (repoUrl.hostname !== "github.com") return html;
|
||||
|
||||
const [owner, repo] = repoUrl.pathname
|
||||
.replace(/^\/+|\/+$/g, "")
|
||||
.split("/");
|
||||
if (!owner || !repo) return html;
|
||||
|
||||
const repoWebBase = `https://github.com/${owner}/${repo}`;
|
||||
const repoBlobBase = `${repoWebBase}/blob/${branch}`;
|
||||
const repoRawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
const githubRepoRoutePrefixes = new Set([
|
||||
"actions",
|
||||
"blob",
|
||||
"branches",
|
||||
"commit",
|
||||
"commits",
|
||||
"compare",
|
||||
"discussions",
|
||||
"issues",
|
||||
"labels",
|
||||
"milestones",
|
||||
"packages",
|
||||
"projects",
|
||||
"pulls",
|
||||
"raw",
|
||||
"releases",
|
||||
"security",
|
||||
"tags",
|
||||
"tree",
|
||||
"wiki",
|
||||
]);
|
||||
const shouldSkipRebase = (value) =>
|
||||
!value ||
|
||||
value.startsWith("#") ||
|
||||
value.startsWith("//") ||
|
||||
/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(value);
|
||||
|
||||
const resolveRepoPath = (value) => {
|
||||
if (shouldSkipRebase(value)) return null;
|
||||
try {
|
||||
const resolved = new URL(value, "https://repo-root.invalid/");
|
||||
return `${resolved.pathname.replace(/^\/+/, "")}${resolved.search}${resolved.hash}`;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const isRepoRoutePath = (repoPath) => {
|
||||
const pathOnly = repoPath
|
||||
.split(/[?#]/, 1)[0]
|
||||
.replace(/^\/+|\/+$/g, "");
|
||||
if (!pathOnly) return false;
|
||||
const firstSegment = pathOnly.split("/")[0].toLowerCase();
|
||||
return githubRepoRoutePrefixes.has(firstSegment);
|
||||
};
|
||||
|
||||
doc.querySelectorAll("a[href]").forEach((anchor) => {
|
||||
const href = (anchor.getAttribute("href") || "").trim();
|
||||
const repoPath = resolveRepoPath(href);
|
||||
if (!repoPath) return;
|
||||
const base = isRepoRoutePath(repoPath) ? repoWebBase : repoBlobBase;
|
||||
anchor.setAttribute("href", `${base}/${repoPath}`);
|
||||
});
|
||||
|
||||
doc.querySelectorAll("img[src]").forEach((image) => {
|
||||
const src = (image.getAttribute("src") || "").trim();
|
||||
const repoPath = resolveRepoPath(src);
|
||||
if (!repoPath) return;
|
||||
image.setAttribute("src", `${repoRawBase}/${repoPath}`);
|
||||
});
|
||||
|
||||
return doc.body.innerHTML;
|
||||
},
|
||||
|
||||
_pluginPrimaryTag(plugin) {
|
||||
const tags = Array.isArray(plugin?.tags) ? plugin.tags.filter(Boolean) : [];
|
||||
return tags[0] || "";
|
||||
|
|
@ -589,9 +504,10 @@ const model = {
|
|||
if (!response.ok) continue;
|
||||
|
||||
const readme = await response.text();
|
||||
let html = marked.parse(readme, { breaks: true });
|
||||
html = this._rebaseReadmeLinks(html, plugin?.github, branch);
|
||||
this.readmeContent = addBlankTargetsToLinks(html);
|
||||
this.readmeContent = renderSafeMarkdown(readme, {
|
||||
githubUrl: plugin?.github,
|
||||
branch,
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue