diff --git a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx index 8fbbae56124..68c7d643c52 100644 --- a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx @@ -141,11 +141,18 @@ const TEAM_COLORS: Record = { Finance: "bg-green-100 text-green-700", }; +/** Sanitize a string for safe interpolation into YAML: strip newlines and + * escape backslash + double-quote so injected content cannot break out of + * a YAML value or introduce extra keys. */ +function yamlSafe(s: string): string { + return s.replace(/[\r\n]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"'); +} + function buildEquivalentConfigYaml(g: TeamGuardrail): string { const lines: string[] = [ "litellm_settings:", " guardrails:", - ` - guardrail_name: "${g.name.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`, + ` - guardrail_name: "${yamlSafe(g.name)}"`, " litellm_params:", ` guardrail: ${g.guardrailType ?? "generic_guardrail_api"}`, ` mode: ${g.mode ?? "pre_call"} # or post_call, during_call`, @@ -160,20 +167,20 @@ function buildEquivalentConfigYaml(g: TeamGuardrail): string { if (g.customHeaders.length > 0) { lines.push(" headers: # static headers (sent with every request)"); for (const h of g.customHeaders) { - lines.push(` ${h.key}: "${String(h.value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`); + lines.push(` ${yamlSafe(h.key)}: "${yamlSafe(String(h.value))}"`); } } if (g.extraHeaders.length > 0) { lines.push(" extra_headers: # forward these client request headers to the guardrail"); for (const name of g.extraHeaders) { - lines.push(` - ${name}`); + lines.push(` - ${yamlSafe(name)}`); } } if (g.additionalProviderParams && Object.keys(g.additionalProviderParams).length > 0) { lines.push(" additional_provider_specific_params:"); for (const [k, v] of Object.entries(g.additionalProviderParams)) { - const val = typeof v === "string" ? `"${v}"` : String(v); - lines.push(` ${k}: ${val}`); + const val = typeof v === "string" ? `"${yamlSafe(v)}"` : String(v); + lines.push(` ${yamlSafe(k)}: ${val}`); } } return lines.join("\n"); diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx index 1b310a4302f..e1918447bb4 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/ChatUI.tsx @@ -340,20 +340,8 @@ const ChatUI: React.FC = ({ proxySettings, ]); - // Note: this debounced chatHistory persistence was part of the original - // security-fixes branch (litellm_security_fixes_v1.82.3) and is ported - // as-is to keep the cherry-pick faithful. It prevents the synchronous - // sessionStorage writes that were flagged by the security scan. - useEffect(() => { - if (simplified) return; // Do not persist chat history in simplified (embedded) mode - const handler = setTimeout(() => { - setObfuscated("chatHistory", JSON.stringify(chatHistory)); - }, 500); // Debounce by 500ms - - return () => { - clearTimeout(handler); - }; - }, [chatHistory, simplified]); + // chatHistory persistence is handled by useChatHistory.ts (debounced, + // with empty-array guard to avoid defeating clearChatHistory()). useEffect(() => { setObfuscated("apiKeySource", JSON.stringify(apiKeySource));