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 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 typeCSP directiveControlled?
rel=prefetchprefetch-srcYes — blocks if URL not in allowlist
rel=preloadprefetch-src (also default-src fallback)Yes — same allowlist
rel=preconnectNoneNo CSP restriction exists
rel=dns-prefetchNoneNo CSP restriction exists
rel=modulepreloadscript-srcYes — 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

CRITICAL −22<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
HIGH −18<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
HIGH −16<link rel="dns-prefetch"> not stripped — DNS resolution triggered for attacker-supplied hostnames, enabling internal hostname enumeration and DNS-based SSRF reconnaissance against internal infrastructure
MEDIUM −12Missing CSP prefetch-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 request
MEDIUM −10State-changing GET endpoints in the MCP backend API — GET requests are CSRF-exploitable via prefetch injection; GET semantics should be read-only; all state mutations should use POST/PUT/DELETE with CSRF token validation
LOW −6rel=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 cookies

See also: CSRF security · SSRF security · Beacon API security · CSP security

Run a free SkillAudit on your MCP server →