Changelog — localproxy
Version history of localproxy — the local HTTP proxy for the JPKCom Developer Tools.
Version history of proxy-jpkcom-dev-tools (opens in a new tab). Entries are taken verbatim from the project's README. A hands-on guide with tips is available in the project guide.
v1.3.0
Feature release: parity with the PHP proxy of the JPKCom Tools (proxy.php
2.8.0) for SSL-certificate and redirect information, so the tools' Expert Mode
shows the same details as their server-side mode. Same toolchain as v1.2.4
(Go 1.27.3); no new dependencies.
Release order — deploy the tools first. /proxy no longer forwards
Location (see below). The updated JPKCom Tools frontend works with older
proxies as before (unknown query parameters such as follow=1, diag=1 and
get=1 are ignored, every new field is feature-detected), but following
redirects needs v1.3.0: with an older proxy the browser still follows a 3xx
from /proxy to the target itself, and a redirect to another site fails at the
CORS preflight ("Failed to fetch"), exactly as with the previous frontend.
Publish the frontend first, then this release; the frontend's MIN_VERSION
moves to v1.3.0.
New
- Full certificate report on
/inspectand/page(ssl): every chain entry gets position, label (Server certificate,Intermediate CA,Root CA (self-signed),Cross-signed root (acts as intermediate)), whether the next certificate signed it, subject/issuer DN and organisation, validity as text and Unix time, serial, OpenSSL signature-algorithm name, key description, SANs, SHA-256/SHA-1 fingerprints and PEM. The report addsprotocolandcipherin OpenSSL notation (TLSv1.3,ECDHE-RSA-AES128-GCM-SHA256),cipherSuite(IANA),bits,verified/valid,error/errorType,hostMatch(RFC 6125),selfSigned,issues(the PHP rule set and messages) andcertSource. Seesslobject (opens in a new tab). diag=1on/inspectand/page: a certificate checker needs details exactly when verification fails. Withdiag=1,/inspectreports an upstream failure inline (HTTP 200,errorobject), and atls_erroron either endpoint runs a handshake-only TLS diagnostic probe (opens in a new tab) that fillssslwith what the failing server presented — also for servers stuck on TLS 1.0/1.1, 3DES or RC4. Withoutdiag, the 502/504 contract is unchanged.typeon error responses: a human-readable class with the PHP proxy's texts (Certificate expired,Could not resolve host, …), set on every upstream failure. See Error responses (opens in a new tab).follow=1on/proxy(GET and HEAD): the proxy follows up to 5 redirects itself, each target SSRF-checked, and reportsX-Upstream-Final-URLandX-Upstream-Redirects. See Redirects on/proxy(opens in a new tab).X-Upstream-Locationon every 3xx from/proxy: the resolved, absolute redirect target./inspect:method,location(resolved 3xx target), and withbody=1truncated,maxBodySizeanddecodeError.get=1takes the headers from a GET (body closed unread) instead of a HEAD, as the PHP proxy's headers-only fetch does — some servers, e.g.www.google.com, sendStrict-Transport-Securityonly on GET./page: hops gainmethod,location,errorCategory,errorTypeand a certificate summary (certCN,certExpiry,certDaysLeft); the response gainstruncated,maxBodySizeanddecodeError.
Behaviour changes
All JSON changes are additive — no key was renamed, removed or retyped — but some values and statuses change:
/proxynever forwardsLocation. The browser'sfetch()followed it straight to the target, carryingX-Proxy-Tokenin a preflighted CORS request — leaking the token wherever the target's CORS policy let it through, and failing with "Failed to fetch" or a proxy-local 404 everywhere else. A 3xx now reaches the caller as such, withX-Upstream-Location; passfollow=1to have the proxy follow it.- Timeouts are reported as
timeout(504) on every endpoint, including dial, TLS-handshake and response-header timeouts, which used to beconnection_errororupstream_error(502); a DNS lookup that times out (e.g. an unreachable--dnsserver) used to bedns_error(502). On/pagethis also applies to hop errors and the final GET, whoseerror.statuswas always 502. /pagereports the real error code. A chain failure used to arrive asupstream_errorwhatever happened; nowerror.erroris the failed hop'serrorCategory(tls_error,dns_error,timeout,redirect_loop,too_many_redirects,blocked_target, …), withstatus504 for a timeout. On a failureipnames the server that failed./pagestops when a 21st redirect follows with atoo_many_redirectshop; exactly 20 redirects still reach the final page. Before, the request after the 20th redirect was fetched as the final page even if it redirected again./pagetreats an unusableLocationas an error. An unparseable value (bad_request) or a non-http(s) scheme (blocked_target) ends the chain; before, the tracer stopped silently and fetched the redirecting URL again as the final page.- HEAD→GET fallback:
/inspectand every/pagehop repeat a request that the server refused with 405 or 501 as GET (headers only, body closed unread), as the PHP proxy does. rawHeaders(top level and per hop) starts with the status line (HTTP/1.1 301 Moved Permanently,HTTP/2 200) and lists headers sorted by name; Go's map order made it differ from call to call.- No more binary
html/body: a response in a registered coding Go cannot decode (br,zstd,compress,dcb,dcz, …), in stacked codings (gzip, br), or a corrupt gzip/deflate stream now givesdecodeError: trueand only the decoded text. Before, the raw compressed bytes were returned as the page. An unregistered token (Content-Encoding: none,UTF-8, …) still passes a plain body through as before, but only if it is valid UTF-8; binary bytes behind such a token now givedecodeError: trueand an empty body. See Compression (opens in a new tab). - Truncated bodies (
/pagehtml,/inspectbody) are cut on a UTF-8 character boundary and flagged withtruncated: true. daysLeftis floored, matching the PHP proxy (differs only for expired certificates, which earlier versions never reported). It is computed on whole Unix seconds, so aNotAfterof 9999-12-31 (RFC 5280's "no expiration") is no longer capped at 106751 days.ssl.chainlists at most 10 certificates (earlier versions listed every certificate the server sent).chain[].subject/issuerfall back to the organisation, then"Unknown", when a certificate has no CN (was"").
Security
- Token leak via
Locationclosed (see above). - Handshake-only TLS probe. This is the one place the proxy skips
certificate verification, and it is deliberately narrow: only with
diag=1after atls_error, only to the IP the failed request had already connected to, through the samesafeDialControlSSRF guard, bounded to 10 s, and the connection never carries application data. Every connection that carries HTTP data stays verified; tests enforce both rules. See TLS diagnostic probe (opens in a new tab). - Redirect targets followed by
/proxy?follow=1and/pagemust be http/https; userinfo is dropped, and each target passes the SSRF pre-check before it is requested.
Fixes
X-Upstream-IPandipcould name the wrong server: with Happy Eyeballs, a losing dial's lateConnectDoneoverwrote the winner's address. Only a successful connect is recorded now./inspect?body=1timing included the HEAD encoding probe; it now describes the GET alone.- The specific certificate messages ("TLS certificate signed by unknown
authority", "TLS certificate invalid", "TLS certificate hostname mismatch")
were unreachable, because the wrapping
*tls.CertificateVerificationErrorwas tested first; every certificate failure read "TLS certificate verification failed". - An
https://URL pointing at a plain-HTTP server was still reported asupstream_error:http.Clientreports that case ashttp.ErrSchemeMismatchrather than thetls.RecordHeaderErrorthat v1.2.1 handled. It is atls_error("TLS protocol not supported") now. /proxycut a compressed chunked body (Content-Encodingset, noContent-Length) at--max-mbwithout saying so. It now setsX-Upstream-Truncated: 1, andX-Upstream-Content-Lengthis the capped size.
Known limitations
X-Upstream-Truncatedis not set for a chunked/proxybody withoutContent-Encoding: that body is streamed, so the headers are already sent when the cap cuts it. A body withContent-Length, and a compressed chunked body (buffered before the headers go out), are flagged.- The diagnostic probe cannot describe servers Go refuses to handshake with even
without verification (negative serial numbers, RSA keys below 1024 bit, SHA-1
handshake signatures in TLS 1.2, SSLv3);
sslthen comes from the verification error or staysnull. - Internationalised host names must arrive in punycode for
hostMatchand the probe's SNI; the standard library has no IDNA.
Tests
- New test PKI with real
httptestTLS servers, covering the certificate report, every TLS verdict, the probe's guarantees, and the new/inspect,/pageand/proxybehaviour — see Testing (opens in a new tab). All earlier tests pass unchanged apart fromextractSSLInfogaining its host argument.
Build & CI
- No toolchain change: Go 1.27.3, CI pin
go-version: "1.27" gofmt,go vet,staticcheck,govulncheckandgo test -racepass
v1.2.4
Security release. Toolchain-only: no changes to main.go or main_test.go,
no client-side adjustments needed. Built with Go 1.27.3 (2026-10-19), an
URGENT-track (opens in a new tab) release with a single
security fix to crypto/x509. Every earlier binary is affected — please update.
CVE-2026-111943 — distrusted intermediate certificates
(go.dev/issue/82345 (opens in a new tab)). Go now blocks trust in a
small set of intermediate CA certificates "due to specific security concerns
around their operation". Certificate.Verify drops every chain that contains
one of them, matched by the SHA-256 hash of the certificate's public key (SPKI).
The filter applies on every platform: the pure-Go verifier (Linux, BSD) and the
macOS and Windows platform verifiers alike.
Relevant — the proxy verifies the certificate of every target. TLS
verification is always on, so all three endpoints (/proxy, /inspect,
/page) rely on crypto/x509 to decide whether a target is trustworthy. Before
this release, a certificate issued through one of these intermediates was
accepted as valid. /inspect and /page also reported it as valid in their SSL
details. Now the handshake fails and the client gets a tls_error with the
message "TLS certificate verification failed". The error format is unchanged.
Corrected in v1.3.0: this note originally named the message "TLS certificate
invalid". The underlying error is an x509.CertificateInvalidError, but the
v1.2.4 classifier tested the wrapping *tls.CertificateVerificationError first,
so only the generic message was reachable. From v1.3.0 on, the specific x509
checks run first: the message is "TLS certificate invalid" and type is
"TLS/SSL error".
govulncheck does not flag this yet: at release time the Go vulnerability
database had no entry for CVE-2026-111943, so the scan is clean on 1.27.2 and
1.27.3 alike. The upgrade follows the Go team's announcement, not a scanner
finding.
Build & CI
go.modgo directive bumped fromgo 1.27.2togo 1.27.3- CI pin stays
go-version: "1.27"and resolves to 1.27.3 automatically - The staticcheck recipe (v0.8.1 against
golang.org/x/toolsv0.51.0) works unchanged on 1.27.3 gofmt,go vet,staticcheck,govulncheck,go test -raceand cross-compilation for all release targets pass on Go 1.27.3
v1.2.3
Security release. Toolchain-only: no changes to main.go or main_test.go,
no client-side adjustments needed. Built with Go 1.27.2 (2026-10-08), which
carries security fixes to the go command and to crypto/tls, html/template,
net/http, net/textproto and os. Every v1.2.x binary built with Go 1.27.0
or 1.27.1 is affected — please update.
govulncheck against the 1.27.1 standard library reports nine advisories whose
symbols this binary reaches, plus four in imported packages that the code does
not call. With 1.27.2 it reports none. Reachable symbols do not mean every one
of them can be triggered against the proxy. Assessed against its actual setup —
an HTTP/1-only server on loopback, and an HTTP/2-capable client to arbitrary,
user-supplied targets:
Relevant — the proxy is the HTTP/2 client of a potentially hostile target
- GO-2026-6610 / CVE-2026-78660 — HTTP/2 transport accepted malformed
framing-related headers. An HTTP/2 response could carry malformed
framing-related headers such as
Content-Length./proxyforwards the target'sContent-Lengthto the browser over HTTP/1, which is exactly the response-smuggling pattern the advisory describes for proxies. (Transfer-Encodingwas already stripped as a hop-by-hop header.) With 1.27.2 such a response fails as an upstream error and is no longer passed through. - GO-2026-6611 / CVE-2026-78669 — CPU exhaustion from repeated
SETTINGS_INITIAL_WINDOW_SIZEchanges. This affects HTTP/2 clients as well as servers. A malicious target could keep the proxy busy for the length of a request. The per-request timeout limited this; it is now fixed outright.
Reachable in the call graph, not exploitable in this setup
- HTTP/2 server issues — GO-2026-6617 (HPACK encoder race → crash),
GO-2026-6612 (double flow-control refund), GO-2026-6603 (memory exhaustion via
Trailer). The proxy serves plain HTTP on127.0.0.1without TLS and does not enable unencrypted HTTP/2, so its server never speaks HTTP/2. - CONNECT desync — GO-2026-6613 (server, 2xx to CONNECT) and GO-2026-6605 (client, CONNECT with a body). The proxy rejects CONNECT with 405 and never sends it upstream; only GET, HEAD and POST are accepted.
- GO-2026-6607 — ECH outer extensions (
crypto/tls): server-side ClientHello decoding. The proxy is only ever a TLS client. - GO-2026-6608 — MIME header memory limit (
net/textproto): triggered by multipart form parsing, which the proxy does not do.net/textprotoalso sits in the HTTP/1 header path, so the fix applies there too.
Not reached — GO-2026-6609 (Range parsing in ServeContent/ServeFile),
GO-2026-6604 (os.Root junctions, Windows), GO-2026-6599 and GO-2026-6600
(html/template). The go command fix affects building only. CI builds this
repository's own stdlib-only code and fetches no third-party modules into the
binary.
Build & CI
go.modgo directive bumped fromgo 1.27.1togo 1.27.2govulncheckreports no findings on the 1.27.2 toolchain (nine reachable advisories on 1.27.1)- CI pin stays
go-version: "1.27"and resolves to 1.27.2 automatically - staticcheck build fixed for Go 1.27.2. The toolchain writes export data
version 5, and staticcheck v0.8.1 still pins
golang.org/x/toolsv0.44, which stops at version 4. Without this fix theverifyjob fails on every push. CI now builds staticcheck v0.8.1 in a throwaway module againstgolang.org/x/toolsv0.51.0 (replacing thev0.8.0-rc.1pin). See Linting (opens in a new tab). gofmt,go vet,staticcheck,go test -raceand cross-compilation for all release targets pass on Go 1.27.2
v1.2.2
Toolchain-only release: no code changes, no behaviour changes, no client-side adjustments needed. Built with Go 1.27.1 (2026-09-01), a regular bug-fix point release — explicitly not a security release, it carries no CVE fix. The move keeps the binaries on the current patch level of the 1.27 series.
Of the twelve backported fixes, three touch packages the proxy imports; none of them has an observable effect on it:
net/http— Go issue #81027. Since 1.27.0,Request.Body.Closeon the server side returnedio.EOFinstead ofnilwhen a handler left part of the body unread (a refactor dropped the line that cleared it)./proxyhands the incoming POST body to the upstream request, and the transport closes it without checking that error on its failure paths — so the regression was invisible here. Fixed nonetheless.encoding/json— Go issues #81083 and #81012. A quotednullwas rejected for fields tagged,string, andDecoder.Tokenlostio.ErrUnexpectedEOFafter the move to the v2 backend. Both are decoding paths; the proxy only encodes JSON, and uses neither,stringtags norDecoder.Token.
The rest does not apply: database/sql, debug/elf and simd are not
imported; the compiler fixes cover generic methods on alias receivers and
s390x/arm64 SIMD code generation, and the go fix fixes touch a build-time
tool only.
Build & CI
go.modgo directive bumped fromgo 1.27.0togo 1.27.1govulncheckreports no findings on the 1.27.1 toolchain- CI pin stays
go-version: "1.27"and resolves to 1.27.1 automatically
v1.2.1
Fixed — TLS handshake failures were misreported
classifyUpstreamError only recognised certificate problems as
tls_error. Every other way a TLS handshake can fail was mislabelled, so the
frontend could not tell "this host is unreachable" from "this host and I cannot
agree on TLS":
| Target | Before | Now |
|---|---|---|
| Server stuck on TLS 1.0 / 1.1 | upstream_error |
tls_error |
| No mutually supported cipher suite | connection_error |
tls_error |
Peer alert (handshake_failure, certificate_required, …) |
connection_error |
tls_error |
Plain HTTP server behind an https:// URL |
upstream_error |
tls_error |
| Connection refused / reset, DNS, certificates | unchanged | unchanged |
Two separate causes, because errors.As cannot see either one:
crypto/tlsdelivers peer alerts as a*net.OpErrorwithOpset to"remote error"or"local error", wrapping the unexportedtls.alerttype — the exportedtls.AlertErrornever matches, soOpis the only usable marker. These were being swallowed by the generic*net.OpErrorcheck.- Failures the client rejects locally are bare
fmt.Errorfvalues with no type whatsoever (261 of them in Go 1.27), such astls: server selected unsupported protocol version 301. The only marker left is the package-widetls:message prefix, which is read off the innermost error — never the flattened chain, because the wrapping*url.Errorcontains the caller-supplied URL and a crafted query string could otherwise forge a TLS verdict.
Verified against real failing handshakes (httptest in the test suite, plus
badssl.com endpoints end-to-end); certificate, DNS, SSRF and genuine socket
errors keep their existing codes.
Note this only affects the error code. HTTP status stays 502 and the
message field remains display-safe, so no frontend change is required.
Corrected in v1.3.0: the "Plain HTTP server behind an https:// URL" row only
held for a bare TLS handshake. Through http.Client that case surfaces as
http.ErrSchemeMismatch and stayed upstream_error until v1.3.0.
v1.2.0
Toolchain release: the proxy moves to Go 1.27.0 (2026-08-19). No code changes, no API changes, no client-side adjustments — every endpoint, field and error code behaves exactly as in v1.1.1.
This is explicitly not a security release. Go 1.27.0 carries no stdlib fix
that was not already in Go 1.26.6; the ten-CVE batch documented under v1.1.1
shipped in both. govulncheck reports no findings on either toolchain. The
reason to move is that Go 1.26 reaches end of life when Go 1.28 lands (~February
2027), and 1.27 is where the fixes will arrive from now on.
Breaking for macOS 12 users
- Go 1.27 discontinued support for macOS 12 Monterey and older; the darwin binaries now require macOS 13 Ventura or later. On an older macOS, stay on v1.1.1 (opens in a new tab) — it is functionally identical. No other platform in the release matrix changed its minimum requirement.
Behaviour changes inherited from the toolchain
crypto/x509on macOS and Windows now honoursSSL_CERT_FILEandSSL_CERT_DIR. When either is set, roots are read from disk and verification switches from the platform APIs to the pure-Go verifier. This is the one change that can alter what/inspectand/pagereport about a certificate chain — but only for users who have those variables exported (corporate MITM proxies, Homebrew OpenSSL setups).GODEBUG=x509sslcertoverrideplatform=0restores the old behaviour. Linux was already reading these variables.- ML-DSA signature schemes (
MLDSA44/65/87, FIPS 204) are now advertised by default in TLS 1.3 ClientHellos. Verified against TLS 1.3 and TLS 1.2-only targets with no interop change; the negotiated suite and everything/inspectreports undersslare unaffected. - HTTP/1 response bodies are auto-drained on
Close, capped at 256 KB and 50 ms, to improve connection reuse. It runs in the transport's background read loop and never blocks the handler —Closereturns in microseconds either way (measured). The drain is skipped whenContent-Lengthis above 256 KB, but a chunked response reportsContent-Length: -1and therefore passes that gate: aborting a chunked body early — which is exactly what the--max-mbtruncation path does — now pulls up to ~256 KB more from the target in the background before the connection is dropped. Bounded, off the critical path, and invisible in the response, but it is extra upstream traffic that Go 1.26 did not generate.DisableKeepAlivesdoes not suppress it;ForceAttemptHTTP2keeps most HTTPS targets on h2, which never enters this loop at all. net/httpservers cap header values at 500 per request (DefaultMaxHeaderValueCount). Inbound hardening on the local listener only; no browser request comes close.- Legacy TLS escape hatches are gone for good: the
tlsrsakex,tls3des,tls10server,tlsunsafeekmandx509keypairleafGODEBUG settings were removed. All had been off by default since Go 1.22/1.23, so nothing changes in practice — a target that only speaks RSA key exchange or 3DES was already unreachable. encoding/jsonis now backed by the v2 implementation. Output is unchanged: HTML escaping, sorted map keys,nullfor nil slices and U+FFFD replacement for invalid UTF-8 in header values all verified identical. Only the text of internal error messages may differ, and those are never sent to the client.
Build & CI
go.modgo directive bumped fromgo 1.26.6togo 1.27.0- CI pin moved from
go-version: "1.26"to"1.27"in both jobs staticcheckis now pinned to@v0.8.0-rc.1instead of@latest: the current stable release (2026.1 / v0.7.0) cannot read Go 1.27 export data and aborts on every stdlib import. Revert to@latestonce 2026.2 is stable.gofmt,go vet,staticcheck,govulncheckand the full test suite pass on 1.27.0 with no source changes
v1.1.1
Toolchain-only release: no code changes, no behaviour changes, no client-side adjustments needed. Built with Go 1.26.6 (2026-08-13), which carries ten security fixes. Four of them touch code paths this proxy actually executes:
encoding/asn1— CVE-2026-33818.Unmarshalhad no recursion limit, so a deeply nested structure could exhaust the stack./inspectand/pageparse the certificate chain of whatever host the caller names, so a hostile TLS server was a plausible route to crashing the proxy.net(vendoreddns/dnsmessage) — CVE-2026-46600. Parsing a malformed SVCB or HTTPS record with an overflowing parameter value could panic. The proxy uses the pure-Go resolver (PreferGo: true) against the servers given via--dns, and the parser walks every record in a reply — not just the A/AAAA answers it asked for.net/url— CVE-2026-56860.resolvePathwas quadratic in the number of..segments./pageresolves eachLocationheader against its base URL while tracing the redirect chain, so a target server could bill the proxy for the CPU time. The 20-hop limit bounded the number of resolutions but not the cost of one.net/http— CVE-2026-56853.ReadHeaderTimeoutwas not applied while the server sniffed for an HTTP/2 preface on a new cleartext connection. The proxy setsReadHeaderTimeout: 10sand serves cleartext HTTP, so the gap was real — though only reachable from the local machine, since the listener binds to127.0.0.1and the Host header must be loopback.
The remaining six do not apply: encoding/xml and html/template are not
imported; x/net/idna (CVE-2026-39821) affects hostname normalisation, but the
SSRF boundary sits in the dialer after resolution and cannot be steered by a
label-parsing quirk; crypto/tls KeyUpdate flooding (CVE-2026-56862) targets
TLS servers and the proxy is a client; the two x/mod/sumdb transparency-log
issues (CVE-2026-56864, CVE-2026-56865) affect the go command at build time,
not the shipped binary — relevant to CI, which now runs on the fixed toolchain.
Build & CI
go.modtoolchain directive bumped fromgo 1.26.5togo 1.26.6govulncheckreports no findings on the 1.26.6 toolchain- CI pin stays
go-version: "1.26"and resolves to 1.26.6 automatically
v1.1.0
Security-focused release. No changes are required in client code — existing fields keep their meaning and only additive fields were introduced.
New
/inspectand/pagereport anhttp3boolean derived from the target'sAlt-Svcfield (RFC 7838, ALPN tokenh3per RFC 9114). This answers "does this site support HTTP/3?" without a QUIC stack — see HTTP/3 detection (opens in a new tab). Draft tokens likeh3-29are ignored.
Security
- SSRF is now enforced in the dialer, after DNS resolution and before connect. The previous hostname pre-check resolved the name separately from the HTTP client, so an attacker-controlled zone with a short TTL could answer with a public address for the check and a loopback address for the connection. The pre-check remains as a fast-fail path only.
- Blocked ranges extended: reserved and special-purpose IPv4 space
(
0/8,192.0.0.0/24,198.18/15, multicast,240/4) plus the IPv6 forms that embed an IPv4 address — IPv4-mapped, NAT64 (64:ff9b::/96) and 6to4 (2002::/16).64:ff9b::7f00:1reaches127.0.0.1on a NAT64 network and was previously invisible to a v4-only check. - Host-header check: requests must address the proxy as
127.0.0.0/8,localhostor[::1], blocking DNS rebinding against the proxy itself. Override with--allow-any-host. - Credential and identity headers are no longer forwarded to targets:
Cookie,Cookie2,Authorization,Proxy-Authorization,Origin,RefererandSec-Fetch-*. Headers the caller sets deliberately —Accept,User-Agent,Accept-Language, customX-*— still pass through. - Bounded work per request:
/pagederives one overall time budget from the client's request context. Redirect hops and encoding probes previously usedcontext.Background(), so a client disconnect stopped nothing and 20 slow redirects could keep a handler alive for minutes. - DNS lookups in the SSRF pre-check are now bounded (5 s) instead of able to hang indefinitely against a black-holed resolver.
- Constant-time token comparison via
crypto/subtle. - Truncated bodies no longer break the response: when a body exceeds
--max-mb, the upstreamContent-Lengthis dropped (it would promise more bytes than are delivered, aborting the connection) andX-Upstream-Truncated: 1is set instead. - Error responses are now readable by the caller: all 4xx responses carry
CORS headers and a JSON body with a machine-readable code
(
bad_request,forbidden,blocked_target,method_not_allowed). A wrong token used to be indistinguishable from an unreachable proxy. Rejected origins still get a bare 403 with no CORS headers, by design. X-Content-Type-Options: nosniffon all proxy-generated responses.
Fixes & quality
Content-Encoding: deflatenow decodes both zlib-wrapped (RFC 1950) and raw DEFLATE (RFC 1951). Only raw was handled, so zlib-wrapped bodies — which a large share of servers send — came back empty or corrupt.- Headers named in a
Connectionheader are treated as hop-by-hop (RFC 9110 §7.6.1). Vary: Originis now always set, not only when a CORS origin was echoed.- SSL info includes IP SANs, not just DNS names.
--port,--timeoutand--max-mbare validated at startup;--timeout 0previously produced an already-expired context and failed every request.- Upstream
Content-Lengthis sanity-checked before being reported astransferSize. - Shared handler preamble replaces the duplicated method/origin/token/URL/SSRF blocks across the three endpoints.
- JSON encoding errors are logged rather than silently discarded.
Build & CI
- Built with Go 1.26.5 — security fixes in
crypto/tls(GO-2026-5856 / CVE-2026-42505, ECH handshake de-anonymisation) andos go.modtoolchain directive bumped fromgo 1.26.4togo 1.26.5- CI now runs on every push and pull request, not only on tags — a broken test no longer surfaces first at release time
govulncheckand agofmtcheck added to CIGITHUB_TOKENscoped to read-only by default; only the release job gets write
v1.0.5
- Built with Go 1.26.4 — picks up upstream stdlib fixes for
crypto/tls,crypto/x509,net/http, and the runtime go.modtoolchain directive bumped fromgo 1.26.3togo 1.26.4
v1.0.4
- Built with Go 1.26.3 — picks up upstream stdlib fixes for
crypto/tls,crypto/x509,net/http, and the runtime go.modtoolchain directive bumped fromgo 1.26.0togo 1.26.3
v1.0.3
- Hardened
/inspectand/pagebody decompression: the decompressed stream is now also capped at--max-mb, preventing zip-bomb-style payloads where a small compressed body would expand to gigabytes of memory - Breaking (JSON shape):
/inspectand/pagenow returnheadersasmap[string][]string(JSON arrays of values) instead ofmap[string]string(joined with", "). This fixes RFC 6265 §3 — multipleSet-Cookieheaders used to be joined with", ", producing an unparseable string because cookie expiry dates contain literal commas (e.g.expires=Sun, 27 Apr 2025 14:03:58 GMT). Browser-side consumers should access values viaheaders["X"][0]for single-value orheaders["X"].join(", ")for display. AArray.isArray()guard makes code compatible with both old and new format. - CI now runs
staticcheck ./...on linux/amd64 before producing release binaries; any finding blocks the release - Code formatted with
gofmt -w(struct alignment cleanup)
v1.0.2
- Built with Go 1.26.2 — includes upstream security fixes for
crypto/tls,crypto/x509,net/url, andnet/http - CI now runs
go test -race -v ./...on linux/amd64 before producing release binaries; failed tests block the release - Expanded README testing section with
-raceand-coverexamples and a summary of whatmain_test.gocovers
v1.0.1
- Added
--versionCLI flag to print version information - Added
/versionendpoint returning version, license, author, and repo as JSON - Added GPL-2.0-or-later license
- Startup banner now shows version, license, author, and repository URL
- Startup banner groups Address/Token, DNS/Origins, and License/Author/Repo into visually separated sections
- Fixed resource safety: response body in redirect chain tracing is now closed immediately after receiving the response
- Added FreeBSD (amd64, arm64), OpenBSD (amd64), and NetBSD (amd64) release binaries
v1.0.0
- Initial release
- Streaming proxy (
/proxy), metadata inspection (/inspect), full page analysis (/page) - Per-session cryptographic token authentication
- Origin allowlist, SSRF protection, configurable DNS resolver
- CORS with Private Network Access support
- Cross-platform binaries via GitHub Actions