diff --git a/web/src/pages/AgentFleetPage.test.tsx b/web/src/pages/AgentFleetPage.test.tsx
new file mode 100644
index 0000000..48bfcb8
--- /dev/null
+++ b/web/src/pages/AgentFleetPage.test.tsx
@@ -0,0 +1,80 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen, waitFor, cleanup } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { MemoryRouter } from 'react-router-dom';
+import type { ReactNode } from 'react';
+
+// -----------------------------------------------------------------------------
+// M-029 Pass 3 (Audit M-026): AgentFleetPage XSS-hardening + render coverage.
+// Agent name / hostname / OS / arch / IP are agent-self-reported (M-003 in
+// the MCP fence path); the GUI rendering must also be XSS-safe.
+// -----------------------------------------------------------------------------
+
+vi.mock('../api/client', () => ({
+ getAgents: vi.fn(),
+}));
+
+import AgentFleetPage from './AgentFleetPage';
+import * as client from '../api/client';
+
+function renderWithQuery(ui: ReactNode) {
+ const qc = new QueryClient({
+ defaultOptions: { queries: { retry: false, gcTime: 0, staleTime: 0 } },
+ });
+ return render(
+
+ {ui}
+ ,
+ );
+}
+
+const xssPayload = '';
+
+const xssAgent = {
+ id: 'a-xss-001',
+ name: xssPayload,
+ hostname: xssPayload,
+ os: xssPayload,
+ architecture: xssPayload,
+ ip_address: xssPayload,
+ version: xssPayload,
+ status: 'online',
+ last_heartbeat_at: new Date().toISOString(),
+ agent_group_id: 'ag-xss',
+};
+
+describe('AgentFleetPage — render + XSS hardening (M-026 / M-029 Pass 3)', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ delete (window as unknown as { __xss_pwned__?: number }).__xss_pwned__;
+ });
+
+ it('renders the page header when getAgents resolves', async () => {
+ vi.mocked(client.getAgents).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 50 } as never);
+ renderWithQuery();
+ await waitFor(() => {
+ expect(screen.getByText(/Agent/i)).toBeInTheDocument();
+ });
+ });
+
+ it('does NOT execute ';
+
+const xssCheck = {
+ id: 'hc-xss-001',
+ endpoint: xssPayload,
+ status: 'failing',
+ last_error: xssPayload,
+ last_checked_at: new Date().toISOString(),
+ acknowledged: false,
+};
+
+describe('HealthMonitorPage — render + XSS hardening (M-026 / M-029 Pass 3)', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ delete (window as unknown as { __xss_pwned__?: number }).__xss_pwned__;
+ vi.mocked(client.getHealthCheckSummary).mockResolvedValue({ total: 0, failing: 0, ok: 0 } as never);
+ });
+
+ it('renders the page header when listHealthChecks resolves', async () => {
+ vi.mocked(client.listHealthChecks).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 100 } as never);
+ renderWithQuery();
+ await waitFor(() => {
+ expect(screen.getByText(/Health/i)).toBeInTheDocument();
+ });
+ });
+
+ it('does NOT execute ';
+
+const xssJob = {
+ id: 'j-xss-001',
+ type: xssPayload,
+ status: 'Failed',
+ certificate_id: xssPayload,
+ agent_id: xssPayload,
+ error_message: xssPayload,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString(),
+};
+
+describe('JobsPage — render + XSS hardening (M-026 / M-029 Pass 3)', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ delete (window as unknown as { __xss_pwned__?: number }).__xss_pwned__;
+ });
+
+ it('renders the page header when getJobs resolves', async () => {
+ vi.mocked(client.getJobs).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 50 } as never);
+ renderWithQuery();
+ await waitFor(() => {
+ expect(screen.getByText(/Jobs/i)).toBeInTheDocument();
+ });
+ });
+
+ it('does NOT execute ';
+
+const xssScanTarget = {
+ id: 'ns-xss-001',
+ name: xssPayload,
+ network_range: xssPayload,
+ ports: '443,8443',
+ agent_id: xssPayload,
+ enabled: true,
+ last_scan_at: new Date().toISOString(),
+ last_scan_status: 'failed',
+ last_scan_message: xssPayload,
+};
+
+describe('NetworkScanPage — render + XSS hardening (M-026 / M-029 Pass 3)', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ delete (window as unknown as { __xss_pwned__?: number }).__xss_pwned__;
+ });
+
+ it('renders the page header when getNetworkScanTargets resolves', async () => {
+ vi.mocked(client.getNetworkScanTargets).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 50 } as never);
+ renderWithQuery();
+ await waitFor(() => {
+ expect(screen.getByText(/Network/i)).toBeInTheDocument();
+ });
+ });
+
+ it('does NOT execute ';
+
+const xssProfile = {
+ id: 'cp-xss-001',
+ name: xssPayload,
+ description: xssPayload,
+ max_ttl_seconds: 3600,
+ allow_short_lived: false,
+ ekus: [xssPayload],
+ key_usages: [xssPayload],
+ san_types: [xssPayload],
+ created_at: new Date().toISOString(),
+};
+
+describe('ProfilesPage — render + XSS hardening (M-026 / M-029 Pass 3)', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ cleanup();
+ delete (window as unknown as { __xss_pwned__?: number }).__xss_pwned__;
+ });
+
+ it('renders the page header when getProfiles resolves', async () => {
+ vi.mocked(client.getProfiles).mockResolvedValue({ data: [], total: 0, page: 1, per_page: 50 } as never);
+ renderWithQuery();
+ await waitFor(() => {
+ expect(screen.getByText(/Profile/i)).toBeInTheDocument();
+ });
+ });
+
+ it('does NOT execute