Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,902 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Forwarder Proxy (fork of Sauce Labs Forwarder)

Fork of the original Sauce Labs Forwarder — a production-ready Go library providing an HTTP forward proxy with MITM (SSL bumping), PAC (Proxy Auto-Configuration), Kerberos authentication, header manipulation, domain filtering, rate limiting, and dual-stack IPv4/IPv6 support.

What this fork solves: The original Forwarder's PAC evaluator relies on the Goja JavaScript engine for all isInNet() subnet matching — each call costs ~130μs/op with 212 allocations due to JS interop overhead. This fork adds pure-Go implementations of the PAC helper functions (isInNet, dnsResolve, etc.) that bypass Goja entirely, delivering ~600ns/op (~217x faster) — critical for high-throughput scenarios with thousands of concurrent connections.

License

Mozilla Public License Version 2.0 — same as the upstream Sauce Labs Forwarder. Third-party licenses in LICENSE.3RD_PARTY.


Installation

go get github.com/cymertek/go-forwarder@latest

Minimum Go version: 1.23

Quick Start

Minimal HTTP Proxy

package main

import (
    "context"
    "log"

    "github.com/cymertek/go-forwarder"
    "github.com/cymertek/go-forwarder/log/slog"
)

func main() {
    cfg := forwarder.DefaultHTTPProxyConfig()
    cfg.Address = ":3128" // listen address

    logger := slog.Default()

    // Create a PACResolver if needed (see pac package section below)
    var pr forwarder.PACResolver = nil

    proxy, err := forwarder.NewHTTPProxy(cfg, pr, nil /* no credentials */, nil /* use default transport */, logger, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer proxy.Close()

    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        <-ctx.Done()
        cancel()
    }()

    log.Printf("proxy listening on %s", proxy.Addr())
    if err := proxy.Run(ctx); err != nil {
        log.Fatal(err)
    }
}

HTTPS Proxy Server with Basic Auth

cfg := forwarder.DefaultHTTPProxyConfig()
cfg.Address = ":8443"
cfg.Protocol = forwarder.HTTPSScheme
cfg.CertFile = "cert.pem"
cfg.KeyFile = "key.pem"
cfg.BasicAuth = url.UserPassword("admin", "secret")

proxy, err := forwarder.NewHTTPProxy(cfg, nil, nil, nil, slog.Default(), nil)

Supported Features

Feature Description Key Types / Functions
Forward proxy HTTP/HTTPS/HTTP2/WS/SSE/TCP traffic NewHTTPProxy, NewHTTPProxyHandler
MITM / SSL bumping On-the-fly certificate generation for HTTPS inspection MITMConfig, proxy.MITMCACert()
PAC support Mozilla FindProxyForURL + Microsoft IPv6 extensions (see below) PACResolver, pac.NewProxyResolver
Upstream chaining HTTP proxy, HTTPS tunnel, SOCKS5 via nested dialers dialvia.HTTPProxy(), dialvia.SOCKS5Proxy()
Kerberos auth SPNEGO/Kerberos for upstream proxy authentication KerberosConfig, NewKerberosAdapter
Header manipulation Add/remove/empty/rename headers on request/response header.Header{}, header.Headers
Domain filtering Block or bypass domains via regex matchers HTTPProxyConfig.DenyDomains, .DirectDomains
Time-frame access control Allow traffic only during specified weekdays/hours ruleset.TimeFrameEntry, .AllowTimeFrame
Credentials matching Per-host/port/basic auth for proxy and upstream CredentialsMatcher, HostPortUser
DNS connect-to / rebinding Redirect dial targets to different hosts/ports DialRedirectFromHostPortPairs, HostPortPair
Rate limiting Bandwidth limits on listener connections ratelimit.NewListener, SizeSuffix
Multiple listeners Bind proxy to multiple addresses simultaneously MultiListener, HTTPProxyConfig.ExtraListeners
Prometheus metrics Request latency, errors, dial counts, TLS cert expiry APIHandler (/metrics)

PAC (Proxy Auto-Configuration) — Fork Enhancements

Interface

type PACResolver interface {
    FindProxyForURL(url *url.URL, hostname string) (string, error)
}

Using the built-in PAC evaluator

import "github.com/cymertek/go-forwarder/pac"

cfg := &pac.ProxyResolverConfig{
    Script: pacScriptString, // your FindProxyForURL JS function
    AlertSink: os.Stderr,
    DNSTTL: 5 * time.Minute, // DNS cache TTL (default 3 minutes if zero)
}
resolver, err := pac.NewProxyResolver(cfg, nil /* use default net.Resolver */)
if err != nil { log.Fatal(err) }

// Evaluate proxy choice for a URL — uses pure-Go IsInNet (~217x faster than JS interop)
result, err := resolver.FindProxyForURL(&url.URL{Scheme: "https", Host: "example.com"}, "")
// result = "PROXY upstream1.example.com:8080; DIRECT"

// Direct pure-Go subnet matching (bypasses Goja entirely):
if resolver.IsInNet("10.5.6.7", "10.0.0.0", "255.0.0.0") { ... }

For concurrent use, wrap in a pool:

// For concurrent use, wrap in a pool (each call gets its own VM)
pool, err := pac.NewProxyResolverPool(cfg, nil)

// Optionally wrap with logging for diagnostics
resolver := &forwarder.LoggingPACResolver{
    Resolver: pool,
    Logger:   slog.Default().Named("pac"),
}

What changed from upstream Forwarder

Function Upstream (Goja interop) This fork (pure-Go) Speedup
isInNet() JS via Goja runtime Go net.ParseIP + bitwise math ~217x
dnsResolve() JS dnsResolve() → Goja interop Direct net.Resolver call with cache ~10-50x
myIpAddress() JS myIpAddress() → Goja interop Direct net.InterfaceAddrs() + cache ~20x

The pure-Go implementations are registered on the Goja VM as native bindings (isInNetGo, convertAddrGo, isValidIpv6), so existing PAC scripts work without modification. The fork also adds:

  • DNS caching: Thread-safe per-resolver cache keyed by hostname with configurable TTL (default 3 minutes).
  • Microsoft IPv6 extensions: isResolvableEx, isInNetEx, dnsResolveEx, myIpAddressEx, sortIpAddressList, getClientVersion — all implemented in Go where possible.
  • Unified subnet matching: isInNetGo() handles dotted-decimal masks, CIDR /N notation, and IPv6 dotted-suffix addresses in a single pure-Go path.

Supported PAC functions

Use pac.SupportedFunctions() to get the complete sorted list of all available functions (Mozilla standard + Microsoft extensions + pure-Go implementations). See goja/README.md for detailed benchmarks and architecture notes.

Architecture

Client → HTTP(S) Proxy (forwarder.HTTPProxy)
                │
        ┌───────┴────────┐
        ▼                ▼
   MITM CA          PAC Resolver (fork: pure-Go paths)
  generates cert    FindProxyForURL + IsInNet() ~600ns/op
        │               │
        ▼               ▼
   Upstream         dialvia chain
   Target            (HTTP/SOCKS5)

Thread Safety

  • HTTPProxy is not safe for concurrent modification after construction. Configure all settings before calling NewHTTPProxy.
  • ProxyResolverPool uses sync.Pool to recycle Goja VM instances — each FindProxyForURL call gets a fresh resolver from the pool, making it safe for concurrent use.
  • CredentialsMatcher is immutable once constructed and safe for concurrent reads.

About

Forwarder is a production-ready, fast MITM proxy with PAC support. It's suitable for debugging, intercepting and manipulating HTTP traffic. It's used as a core component of Sauce Labs Sauce Connect Proxy.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages