Titanium Web Proxy Documentation

repository·develop·Indexed 24 days ago

https://github.com/justcoding121/titanium-web-proxy

A lightweight, asynchronous HTTP(S) proxy server for .NET designed to intercept, inspect, modify, redirect, or block web traffic. It supports Explicit, Transparent, and SOCKS proxy endpoints, as well as HTTP/2 and HTTP/3 (QUIC). Version 4.0.0 targets .NET 10 and includes support for RFC 8441 Extended CONNECT for WebSocket over HTTP/2.

Tokens
15.7K
Snippets
30
Records
82
Agent score
81%

What's inside Titanium Web Proxy

  1. Ensure WebSocket protections are active

    develop

    WebSocket security features (reserved-opcode rejection, pre-buffer frame-size validation, and RFC 6455-conformant close handling) are conditional. They only apply if the proxy is actively parsing the WebSocket frames.

    To ensure these protections are active, you must avoid the following two bypass paths:

    1. Opaque TLS Relay: Do not use decryptSsl: false on the endpoint or tunnel handling the connection. If TLS is not decrypted, the proxy cannot validate the WebSocket content.
    2. Non-HTTP Relay: Avoid using a SocksProxyEndPoint for WebSocket traffic, as non-HTTP traffic is relayed transparently without parsing.
  2. How HTTP/3 (QUIC) bridging works

    develop

    Titanium Web Proxy supports several protocol bridging scenarios to ensure connectivity even when the client and origin do not share the same protocol:

    • Outbound H3 $\rightarrow$ H3: The proxy uses QuicConnectionPool to lease a QuicConnection per origin.
    • Outbound H3 $\rightarrow$ H2: Falls through to TcpConnectionFactory with h2 ALPN when UpstreamHttpProtocol.Http2 is set.
    • Outbound H3 $\rightarrow$ H1.1: Falls through to TcpConnectionFactory with default ALPN negotiation.
    • Inbound H1.1/H2 $\rightarrow$ H3 origin: The RequestHandler checks Http3OriginCapabilityCache. If H3 is cached (e.g., via Alt-Svc discovery), Http3OriginBridge.ForwardAsync is used instead of a TCP connection.
  3. Configure HTTP/2 and HTTP/3 support

    develop

    Titanium Web Proxy provides support for modern HTTP protocols:

    • HTTP/2: Enabled by default. You can opt-out by setting ProxyServer.EnableHttp2 = false.
    • HTTP/3 (QUIC): Must be opted-in by setting ProxyServer.EnableHttp3 = true. This requires MsQuic to be installed on the system.
  4. How HTTP/3 protocol selection works

    develop

    When UpstreamHttpProtocol.Auto (the default) is used, the proxy selects the outbound protocol in this order:

    1. HTTP/3: If EnableHttp3 == true AND the origin's H3 capability is known (via Alt-Svc cache or proactive HTTPS/SVCB DNS discovery).
    2. HTTP/2: If the origin supports HTTP/2 via ALPN.
    3. HTTP/1.1: The fallback protocol.

    Overriding selection:

    • Per-request: Set SessionEventArgs.UpstreamHttpProtocol inside the BeforeRequest event.
    • Per-connection: Set BeforeQuicAuthenticateEventArgs.UpstreamHttpProtocol inside the BeforeQuicAuthenticate event.
  5. Understand HTTP/3 (QUIC) limitations in Titanium Web Proxy

    develop

    When using HTTP/3 in TWP, be aware of the following constraints:

    • Transparent Only: HTTP/3 cannot be used as an explicit (system-proxy) endpoint. This is because current OS proxy APIs and major browsers do not support specifying a UDP-based proxy endpoint, and the standard MASQUE/CONNECT-UDP mechanism acts as a relay that prevents TLS termination/interception.
    • No 0-RTT: Early data is not supported by System.Net.Quic in .NET 10.
    • No Connection Migration: The underlying System.Net.Quic does not expose migration APIs.
    • No Server Push: This feature was removed from RFC 9114.
    • Upstream Proxy Fallback: If an upstream proxy is configured, the QUIC leg will gracefully fall back to TCP (ForwardOverTcpAsync) because System.Net.Quic does not support HTTP CONNECT tunnelling or SOCKS5 UDP ASSOCIATE.
  6. Understand Root CA private key protection limits

    develop

    The Titanium Web Proxy protects the root CA private key by relocating the certificate store to a per-user folder and avoiding passing the PFX password via command-line arguments.

    Security Boundary: This protects the key from other local users on the machine, but it does not protect the key from other processes running under your same OS user account.

    If your threat model requires protection against other processes running as your user (e.g., in a shared CI/CD environment), you must use OS-level mechanisms such as a hardware-backed key store or a dedicated service account.

  7. Use Policy Profiles and Observe/Enforce modes

    develop

    Version 5.0 introduces a way to tune security limits and behaviors using profiles and policy modes without modifying individual settings.

    Policy Profiles

    Set ProxyServer.Profile to apply a bundle of settings:

    • ProxyProfile.Balanced (Default): Reproduces the standard 5.0 security defaults.
    • ProxyProfile.LegacyCompatible: For compatibility with older behaviors.
    • ProxyProfile.PublicFacing: Enables stricter security, such as blocking private network destinations.

    Policy Modes

    Use ProxyServer.PolicyModes to control how specific policy families are handled. You can set a family to:

    • PolicyMode.Observe: Records a metric on a breach but does not reject or close the connection.
    • PolicyMode.Disabled: Disables the check entirely.

    Supported PolicyFamily types include:

    • BodyBudget
    • DecompressionRatio
    • HeaderLimits
    • AdmissionControl
    • Http2AbuseBudget
  8. Understand WebSocket protocol violation handling in 5.0

    develop

    Version 5.0 introduces pre-buffering validation for WebSocket frames. If a frame violates the protocol (e.g., reserved opcodes, oversized declared length, or RSV bits without extensions), the proxy now performs an RFC 6455-compliant Close handshake (status 1002 Protocol Error or 1009 Message Too Big) before tearing down the TCP connection.

    This change is transparent for typical use cases and does not require configuration changes if your MaxWebSocketFramePayloadBytes was already correctly sized.

  9. Understand the LoopbackCertificateAuthority usage

    develop

    The Support/LoopbackCertificateAuthority is a specialized utility used within the benchmark harness. It mints a process-local root and a localhost leaf entirely in memory.

    Important Security Note:

    • It is used exclusively to provide certificate validation for the HTTP/2 benchmark's client and server legs.
    • Nothing is persisted or trusted system-wide.
    • It has no relationship to a real deployment's certificate store.
    • Do not reuse this for anything beyond the benchmark harness.
  10. How SOCKS endpoints handle traffic

    develop

    A SocksProxyEndPoint accepts SOCKS4/5 connections. The behavior for HTTPS traffic depends on the decryptSsl setting:

    • If decryptSsl: true (default): HTTPS (TLS ClientHello detected) is MITM-decrypted, and the HTTP(S) interception pipeline (BeforeRequest/BeforeResponse/AfterResponse) runs.
    • If decryptSsl: false: HTTPS is treated as an opaque TCP relay to the destination; no inspection occurs.

    Plain HTTP and non-HTTP/non-TLS traffic (like SMTP) are generally relayed transparently or processed via the HTTP pipeline depending on the protocol detection.

    var socksEndPoint = new SocksProxyEndPoint(IPAddress.Loopback, 1080, decryptSsl: true);
    socksEndPoint.BeforeSslAuthenticate += (sender, e) =>
    {
        if (e.SniHostName.EndsWith(".internal", StringComparison.OrdinalIgnoreCase))
            e.DecryptSsl = false; // relay opaquely without decrypting
        return Task.CompletedTask;
    };
    proxyServer.AddEndPoint(socksEndPoint);
  11. Understand Body API restrictions on Extended CONNECT tunnels

    develop

    Once an RFC 8441 Extended CONNECT tunnel is successfully established (indicated by a 2xx response from the origin), the stream becomes an unbounded duplex stream.

    Warning: Calling GetRequestBody() or GetResponseBody() on an established tunnel stream will throw an InvalidOperationException. These methods are intended for finite HTTP bodies, whereas tunnels are continuous data streams.

    Note that if the connection is NOT established (i.e., the origin returns a non-2xx response), the response body remains readable via standard methods.

  12. Drain bodies to reuse connections

    develop

    When you short-circuit a message (e.g., via a custom response or redirect), the other side of the connection might still have unread data. To allow the connection to be reused (Keep-Alive), you must "drain" the body by reading and discarding the bytes without buffering them.

    • Use await e.DrainServerBodyAsync() to discard unread bytes from the server response.
    • Use await e.DrainClientBodyAsync() to discard unread bytes from the client request.

    Drain vs. Close:

    • Drain: Reads and discards the body so the connection is reusable. Use this for standard responses.
    • Close: Closes the connection entirely. Use closeServerConnection: true in Respond/RespondStreaming or call e.TerminateServerConnection().

    Warning: Draining an endless chunked body will block until the cancellation token fires or the connection closes. For endless streams, you should close the connection instead of draining it.

    // Read and discard the unread server response body (keeps the server connection reusable).
    await e.DrainServerBodyAsync();
    
    // Read and discard the unread client request body (keeps the client keep-alive connection reusable).
    await e.DrainClientBodyAsync();