address greptile review feedback (greploop iteration 3)

- Remove duplicate chatHistory persistence from ChatUI.tsx that defeated
  clearChatHistory() — useChatHistory.ts already handles this with the
  empty-array guard
- Add yamlSafe() helper in TeamGuardrailsTab.tsx to sanitize all YAML
  interpolated keys/values (strips newlines, escapes backslash + quote)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang 2026-03-23 23:30:19 -07:00
parent 7356bf1e48
commit 75ed872643
2 changed files with 14 additions and 19 deletions

View file

@ -141,11 +141,18 @@ const TEAM_COLORS: Record<string, string> = {
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");

View file

@ -340,20 +340,8 @@ const ChatUI: React.FC<ChatUIProps> = ({
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));