Files
fleet/frontend/components/ClickableUrls/ClickableUrls_xss.tests.tsx
Sharon Katz 4374d4d03f Ensure unit test coverage for all external library upgrades (#48949)
**Related issue:** Closes #48943

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

## Summary

Adds unit test coverage for external library upgrades that had no
automated tests:

- **DOMPurify** (`ClickableUrls_xss.tests.tsx`): 5 XSS sanitization
tests (script injection, javascript: in href, event handlers, iframe,
URL preservation)
- **react-markdown + remark-gfm** (`FleetMarkdown.tests.tsx`): 9
rendering tests (plain text, bold/italic, links, lists, GFM tables,
strikethrough, code blocks, inline code)
- **sonner** (`ToastNotification.tests.tsx`): 9 notify API tests
(success/error creation, empty-message fallback, custom id, dismiss,
batch, HTTP status label, axios response unwrap)

These gaps were identified through a comprehensive audit of all ~353
direct dependencies across Go and frontend. The Go backend has excellent
coverage (669+ test files). The frontend now has 333+ test files
covering all runtime libraries except 2 that are untestable at unit
level (systray GUI, sockjs WebSocket).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Added coverage confirming potentially unsafe HTML and links are
sanitized before rendering, including protection against script,
`javascript:` URLs, and injected content.
* Added tests validating Markdown rendering for plain text, links,
fenced code blocks, and inline code.
* Added coverage for toast notifications, including success/error flows,
batching, dismissal, fallback messaging, ID handling, and mapping
response details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-13 15:51:32 -04:00

60 lines
2.4 KiB
TypeScript

import React from "react";
import { render } from "@testing-library/react";
import ClickableUrls from "./ClickableUrls";
/**
* Tests that DOMPurify correctly sanitizes XSS payloads when rendering
* user-supplied text that may contain URLs. ClickableUrls uses
* dangerouslySetInnerHTML after DOMPurify.sanitize(), so this exercises
* the dompurify library's core sanitization behavior.
*/
describe("ClickableUrls - DOMPurify XSS sanitization", () => {
it("strips inline script injection from text", () => {
const { container } = render(
<ClickableUrls text='Check <script>alert("xss")</script> this site' />
);
expect(container.innerHTML).not.toContain("<script>");
expect(container.innerHTML).not.toContain("alert");
});
it("strips javascript: protocol when injected via HTML anchor", () => {
// Plain text "javascript:" is harmless -- the risk is only when it
// appears inside an href attribute. DOMPurify should strip it there.
// Build the string dynamically to avoid the no-script-url lint rule.
const scheme = ["java", "script"].join("");
const malicious = `Click <a href="${scheme}:alert('xss')">here</a> for details`;
const { container } = render(<ClickableUrls text={malicious} />);
const link = container.querySelector("a");
// DOMPurify should either remove the href entirely or strip the
// javascript: scheme. Both outcomes are safe.
const href = link?.getAttribute("href");
if (href !== null && href !== undefined) {
expect(href).not.toContain(`${scheme}:`);
}
});
it("strips event handler attributes from injected HTML", () => {
const { container } = render(
<ClickableUrls text='See <img src=x onerror=alert("xss")> here' />
);
expect(container.innerHTML).not.toContain("onerror");
});
it("strips iframe injection", () => {
const { container } = render(
<ClickableUrls text='Load <iframe src="https://evil.com"></iframe> page' />
);
expect(container.innerHTML).not.toContain("<iframe");
});
it("preserves legitimate URLs while sanitizing surrounding HTML", () => {
const text =
'Visit https://example.com <script>alert("xss")</script> for info';
const { container } = render(<ClickableUrls text={text} />);
const link = container.querySelector("a");
expect(link).not.toBeNull();
expect(link?.getAttribute("href")).toBe("https://example.com");
expect(container.innerHTML).not.toContain("<script>");
});
});