diff --git a/apps/mesh/package.json b/apps/mesh/package.json index 230814c04e..3a56bc2017 100644 --- a/apps/mesh/package.json +++ b/apps/mesh/package.json @@ -65,6 +65,10 @@ "@dnd-kit/utilities": "^3.2.2", "@inkjs/ui": "^2.0.0", "@modelcontextprotocol/ext-apps": "^1.7.1", + "@nats-io/jetstream": "3.4.0", + "@nats-io/kv": "3.4.0", + "@nats-io/nats-core": "3.4.0", + "@nats-io/transport-node": "3.4.0", "@openrouter/ai-sdk-provider": "^2.9.1", "@opentelemetry/core": "^2.6.0", "@tanstack/react-virtual": "3.13.24", @@ -72,16 +76,13 @@ "@xterm/xterm": "^6.0.0", "embedded-postgres": "^18.3.0-beta.16", "ink": "^6.8.0", - "@nats-io/jetstream": "3.4.0", - "@nats-io/kv": "3.4.0", - "@nats-io/nats-core": "3.4.0", - "@nats-io/transport-node": "3.4.0", "kysely": "^0.28.12", "node-pty": "^1.0.0", "posthog-js": "^1.371.1", "posthog-node": "^5.0.0", "react": "^19.2.6", - "react-dom": "^19.2.6" + "react-dom": "^19.2.6", + "rehype-sanitize": "^6.0.0" }, "devDependencies": { "@ai-sdk/provider": "^3.0.10", diff --git a/apps/mesh/src/web/components/chat/markdown.tsx b/apps/mesh/src/web/components/chat/markdown.tsx index a87b6e2386..878f27386f 100644 --- a/apps/mesh/src/web/components/chat/markdown.tsx +++ b/apps/mesh/src/web/components/chat/markdown.tsx @@ -3,6 +3,7 @@ import { marked } from "marked"; import React, { memo, useCallback, useMemo, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import rehypeRaw from "rehype-raw"; +import rehypeSanitize from "rehype-sanitize"; import remarkGfm from "remark-gfm"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Button } from "@deco/ui/components/button.tsx"; @@ -129,7 +130,10 @@ function Table(props: React.HTMLAttributes) { // Memoize the plugins arrays to prevent re-creating them on every render const remarkPluginsMemo = [remarkGfm]; -const rehypePluginsMemo = [rehypeRaw]; +// rehypeRaw parses raw HTML in markdown; rehypeSanitize (GitHub schema) +// strips dangerous elements (iframe, script, object, embed, form) and +// attributes (srcdoc, on*) to prevent stored XSS via chat messages. +const rehypePluginsMemo = [rehypeRaw, rehypeSanitize]; // Extend shared markdown components with chat-specific overrides (table with CSV copy, image lightbox) const markdownComponents = { diff --git a/bun.lock b/bun.lock index 080b5ea67b..b0624a67b4 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "@decocms/mesh-monorepo", @@ -38,7 +37,7 @@ }, "apps/mesh": { "name": "decocms", - "version": "3.43.5", + "version": "3.52.10", "bin": { "deco": "./dist/server/cli.js", }, @@ -75,6 +74,7 @@ "posthog-node": "^5.0.0", "react": "^19.2.6", "react-dom": "^19.2.6", + "rehype-sanitize": "^6.0.0", }, "devDependencies": { "@ai-sdk/provider": "^3.0.10", @@ -188,7 +188,7 @@ }, "packages/bindings": { "name": "@decocms/bindings", - "version": "1.4.7", + "version": "1.4.9", "dependencies": { "@decocms/mcp-utils": "^1.0.5", "@modelcontextprotocol/sdk": "1.29.0", @@ -211,7 +211,7 @@ }, "packages/harness": { "name": "@decocms/harness", - "version": "1.7.0", + "version": "1.9.2", "dependencies": { "@ai-sdk/anthropic": "^3.0.85", "@ai-sdk/google": "^3.0.83", @@ -232,7 +232,7 @@ }, "packages/mcp-utils": { "name": "@decocms/mcp-utils", - "version": "1.0.9", + "version": "1.0.10", "dependencies": { "@decocms/std": "^0.1.0", "ajv": "^8.20.0", @@ -249,7 +249,7 @@ }, "packages/mesh-sdk": { "name": "@decocms/mesh-sdk", - "version": "1.11.0", + "version": "1.12.0", "dependencies": { "@decocms/bindings": "^1.1.1", "@decocms/mcp-utils": "workspace:*", @@ -268,7 +268,7 @@ }, "packages/runtime": { "name": "@decocms/runtime", - "version": "2.1.2", + "version": "2.1.4", "dependencies": { "@cloudflare/workers-types": "^4.20250617.0", "@decocms/bindings": "^1.0.7", @@ -2232,6 +2232,8 @@ "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + "hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="], + "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], @@ -2898,6 +2900,8 @@ "rehype-recma": ["rehype-recma@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="], + "rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="], + "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],