MCP Server Security · Link Prefetch · Preload · Preconnect · CSRF · SSRF
MCP server link prefetch security — link rel=prefetch credentialed GET requests, preload CSRF side-effects, preconnect SSRF via browser, and CSP prefetch-src in MCP browser UIs
Resource hint link elements injected into MCP tool output can trigger browser network activity without any JavaScript. <link rel="prefetch" href="..."> causes the browser to issue a GET request to the specified URL with the user's full cookie jar attached — enabling CSRF via authenticated GET side-effects. <link rel="preconnect" href="..."> opens a TCP + TLS handshake to the target, which can be used for SSRF reconnaissance against internal services and for port-scanning via connection timing. CSP's prefetch-src directive blocks prefetch but no current CSP directive restricts preconnect. DOMPurify must strip all <link> elements from tool output to close these vectors.
link rel=prefetch — credentialed GET via HTML injection
<link rel="prefetch"> was designed for performance: the browser speculatively fetches resources the page will likely need next. The security problem is that these requests are credentialed — the browser attaches cookies, HTTP authentication headers, and TLS client certificates to the prefetch request, just as it does for any authenticated same-origin navigation.
<!-- Tool output HTML containing a prefetch injection --> <!-- When the MCP UI renders this, the browser fires a credentialed GET to the API endpoint --> <link rel="prefetch" href="https://api.skillaudit.dev/account/delete-all-audits?confirm=true"> <!-- The request carries the user's session cookies — identical to the user clicking a link --> <!-- If the endpoint changes state on GET, this is a CSRF attack without any JavaScript --> <!-- Variants that also trigger credentialed requests: --> <link rel="preload" href="https://api.skillaudit.dev/export/all-data" as="fetch" crossorigin="use-credentials"> <link rel="dns-prefetch" href="https://internal.corp.example.com"> <!-- DNS leak for internal hostnames -->
Prefetch CSRF works without JavaScript. CSP script-src and DOMPurify's default behavior do not prevent <link> injection. Unless your DOMPurify config explicitly sets FORBID_TAGS: ['link'] or FORCE_BODY: true (which strips <head> elements including link), a <link rel="prefetch"> in tool output HTML fires a credentialed request to any URL, bypassing all script-based XSS defenses.
link rel=preconnect — SSRF-lite via TCP/TLS connection
<link rel="preconnect" href="https://target.internal"> instructs the browser to open a TCP connection (and optionally TLS handshake) to the target host. This does not send an HTTP request body, but the TCP + TLS behavior is observable via timing and can be used for:
- Internal host discovery: a preconnect to
https://192.168.1.1succeeds (with measurable latency) if the host is reachable; connection refused or timeout reveals it is not — full subnet scan possible via bulk preconnect injection - TLS certificate probing: the TLS handshake occurs, potentially exposing the target's certificate (Subject Alternative Names reveal internal service names)
- SSRF via connection log: internal services that log incoming TCP connections (even without an HTTP request) record the user's browser as an incoming connection source
- DNS resolution: preconnect causes DNS resolution of the hostname before TCP, enabling internal hostname enumeration via
dns-prefetchvariants
<!-- Internal SSRF via preconnect — no HTTP request, just TCP+TLS handshake --> <link rel="preconnect" href="https://db.internal.corp.example.com"> <link rel="preconnect" href="https://redis.internal.corp.example.com"> <link rel="preconnect" href="https://admin-panel.internal:8443"> <!-- DNS enumeration via dns-prefetch — triggers DNS lookup without TCP connection --> <link rel="dns-prefetch" href="//secrets.internal.corp.example.com"> <link rel="dns-prefetch" href="//vault.internal.corp.example.com">
No current CSP directive restricts link rel=preconnect. CSP's connect-src controls fetch(), XHR, and WebSocket connections but does not apply to resource hint preconnect. CSP's prefetch-src applies to rel=prefetch and rel=preload but not rel=preconnect. The only effective defense against preconnect SSRF is DOMPurify with FORBID_TAGS: ['link'] — blocking link elements from tool output HTML entirely.
CSP prefetch-src and its limitations
CSP prefetch-src restricts which URLs can be targeted by <link rel="prefetch"> and <link rel="preload">. However, it has important coverage gaps:
| Link type | CSP directive | Controlled? |
|---|---|---|
| rel=prefetch | prefetch-src | Yes — blocks if URL not in allowlist |
| rel=preload | prefetch-src (also default-src fallback) | Yes — same allowlist |
| rel=preconnect | None | No CSP restriction exists |
| rel=dns-prefetch | None | No CSP restriction exists |
| rel=modulepreload | script-src | Yes — script-src applies |
// CSP header with prefetch-src — partial mitigation
Content-Security-Policy:
prefetch-src 'self'; // ← blocks cross-origin prefetch/preload
// preconnect and dns-prefetch are NOT controlled by any CSP directive
// Full mitigation: DOMPurify configuration to strip all link elements from tool output
const clean = DOMPurify.sanitize(toolOutputHtml, {
FORBID_TAGS: ['link', 'meta', 'base', 'script', 'style'],
FORBID_ATTR: ['rel', 'href', 'src', 'action', 'formaction'],
// FORCE_BODY: true also works — moves content to body context, stripping head elements
});
SkillAudit findings for link prefetch vulnerabilities in MCP server UIs
<link rel="prefetch"> not stripped from tool output HTML — attacker-controlled tool output fires credentialed GET requests to any URL with user session cookies attached; if any GET endpoint changes state (logout, delete, export), this is a CSRF attack with no JavaScript required<link rel="preconnect"> not stripped from tool output HTML — attacker tool output can probe internal network topology via TCP connection timing; no CSP directive restricts preconnect; DOMPurify default config does not block link elements<link rel="dns-prefetch"> not stripped — DNS resolution triggered for attacker-supplied hostnames, enabling internal hostname enumeration and DNS-based SSRF reconnaissance against internal infrastructureprefetch-src directive — no header-level restriction on which URLs can be prefetched; even if DOMPurify is applied, a bypass that inserts a link element into the DOM has no CSP backstop to block the outbound requestrel=preload not stripped from tool output — as="fetch" crossorigin="use-credentials" variant triggers credentialed fetch to attacker URL; even as="image" fires a credentialed image request leaking the session context to the target server via Referer and cookiesSee also: CSRF security · SSRF security · Beacon API security · CSP security