fix(SEC-WEB-33+35): Fix Mermaid error display + useWorkspaceId error logging
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

SEC-WEB-33: Replace raw diagram source and detailed error messages in
MermaidViewer error UI with a generic "Diagram rendering failed" message.
Detailed errors are logged to console.error for debugging only.

SEC-WEB-35: Add console.warn in useWorkspaceId when no workspace ID is
found in localStorage, making it easier to distinguish "no workspace
selected" from silent hook failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-06 18:16:07 -06:00
parent 014264c592
commit 12fa093f58
4 changed files with 197 additions and 6 deletions

View File

@@ -209,6 +209,84 @@ describe("MermaidViewer XSS Protection", () => {
});
});
describe("Error display (SEC-WEB-33)", () => {
it("should not display raw diagram source when rendering fails", async () => {
const sensitiveSource = `graph TD
A["SECRET_API_KEY=abc123"]
B["password: hunter2"]`;
// Mock mermaid to throw an error containing the diagram source
const mermaid = await import("mermaid");
vi.mocked(mermaid.default.render).mockRejectedValue(
new Error(`Parse error in diagram: ${sensitiveSource}`)
);
const { container } = render(<MermaidViewer diagram={sensitiveSource} />);
await waitFor(() => {
const content = container.innerHTML;
// Should show generic error message, not raw source or detailed error
expect(content).toContain("Diagram rendering failed");
expect(content).not.toContain("SECRET_API_KEY");
expect(content).not.toContain("password: hunter2");
expect(content).not.toContain("Parse error in diagram");
});
});
it("should not expose detailed error messages in the UI", async () => {
const diagram = `graph TD
A["Test"]`;
const mermaid = await import("mermaid");
vi.mocked(mermaid.default.render).mockRejectedValue(
new Error("Lexical error on line 2. Unrecognized text at /internal/path/file.ts")
);
const { container } = render(<MermaidViewer diagram={diagram} />);
await waitFor(() => {
const content = container.innerHTML;
expect(content).toContain("Diagram rendering failed");
expect(content).not.toContain("Lexical error");
expect(content).not.toContain("/internal/path/file.ts");
});
});
it("should not display a pre tag with raw diagram source on error", async () => {
const diagram = `graph TD
A["Node A"]`;
const mermaid = await import("mermaid");
vi.mocked(mermaid.default.render).mockRejectedValue(new Error("render failed"));
const { container } = render(<MermaidViewer diagram={diagram} />);
await waitFor(() => {
// There should be no <pre> element showing raw diagram source
const preElements = container.querySelectorAll("pre");
expect(preElements.length).toBe(0);
});
});
it("should log the detailed error to console.error", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
const diagram = `graph TD
A["Test"]`;
const originalError = new Error("Detailed parse error at line 2");
const mermaid = await import("mermaid");
vi.mocked(mermaid.default.render).mockRejectedValue(originalError);
render(<MermaidViewer diagram={diagram} />);
await waitFor(() => {
expect(consoleErrorSpy).toHaveBeenCalledWith("Mermaid rendering failed:", originalError);
});
consoleErrorSpy.mockRestore();
});
});
describe("DOMPurify integration", () => {
it("should sanitize rendered SVG output", async () => {
const diagram = `graph TD

View File

@@ -86,7 +86,9 @@ export function MermaidViewer({
}
}
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to render diagram");
// Log detailed error for debugging but don't expose raw source/messages to the UI
console.error("Mermaid rendering failed:", err);
setError("Diagram rendering failed. Please check the diagram syntax and try again.");
} finally {
setIsLoading(false);
}
@@ -124,11 +126,8 @@ export function MermaidViewer({
if (error) {
return (
<div className={`flex flex-col items-center justify-center p-8 ${className}`}>
<div className="text-red-500 mb-2">Failed to render diagram</div>
<div className="text-sm text-gray-500">{error}</div>
<pre className="mt-4 p-4 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto max-w-full">
{diagram}
</pre>
<div className="text-red-500 mb-2">Diagram rendering failed</div>
<div className="text-sm text-gray-500">Please check the diagram syntax and try again.</div>
</div>
);
}