Replace the xsync map plus manual expiry on checkCache with
cache.NewKeyFunc(evaluateIP).WithTTL. Move deny/allow/default logic into
evaluateIP; wire getCachedCity and IPAllowed through the cache API.
Refresh README security notes and add tests showing cached decisions persist
across in-memory rule changes until TTL expires.
Add per-provider obtain serialization and an RWMutex around shared
provider fields. GetCert and SNI matching use snapshot helpers; rebuild
the matcher atomically; GetExpiries returns a cloned map.
Clone Config.HTTPClient (including Transport) per lego.Config so parallel
providers do not mutate shared client state.
Document the concurrency model in README.
* test(events): add History Get bounds, AddAll snapshot races, and benchmarks
Assert Get keeps one global window when mixing categories and that Get
during AddAll never reports a partial batch length.
Benchmark History.Add and SnapshotAndListen under background producers;
use WaitGroup.Go in the cancel stress test; group consts in history.go.
* test(synk): refactor pool benchmarks with workerpool
Replace manual work channels and WaitGroup workers with workerpool,
deterministic microsecond jitter, and cpuWork instead of simulateWork.
Adjust concurrent allocation size bands, unsized GetAtLeast, and
ReportAllocs; remove verbose timing metrics. Drop unused newReader in
http body benchmarks.
* fix(io): gate HTTP response flushing on streaming headers
Copy with an http.ResponseWriter destination no longer flushes after
every write unless Content-Type is text/event-stream, or Transfer-Encoding
includes chunked without Content-Length. Buffered responses with
Content-Length skip Flush so fixed-size bodies are not flushed per chunk.
Document the narrowed behavior in README and add tests.
* fix(http): cap modifier buffer preallocation by maxBufferedBytes
When buffering begins, clamp the initial pool buffer size to maxBufferedBytes
when that limit is set. Previously we used max(len(b), Content-Length), which
could reserve a very large slice for a declared body we would never fully
buffer before switching to passthrough.
* fix(http): honor response modifier status in WriteHeader
After a successful modifier run, pass resp.StatusCode to the wrapped writer
instead of the original code so modifiers that change the status code take
effect.
* feat(cache): atomic snapshots, per-refresh backoff, and access-log eviction
Replace mutex-held cached fields with atomic.Pointer snapshots for single-value
and keyed caches so hot reads avoid locking while refreshes stay serialized per
entry.
Build fresh backoff instances per refresh, add context-aware backoff waits, and
skip storing cached values when cancellation matches the returned error.
Keyed caches record access via sequences and logs instead of a global MRU list;
fix janitor pending cleanup when the signal channel is full. Add benchmarks and
tests for cancellation, TTL, and eviction behavior.
* chore(deps): update dependencies
Httptest and similar callers often leave Host unset; fall back to URL
for scheme, host, port, and addr substitution.
jsonstore drops the IsTest load short-circuit and duplicate loadNS map
registration; tests isolate storesPath. Skip MaxMind background updates
when IsTest. Tests restore APISkipOriginCheck, use app-scoped OIDC
state cookies, attach route context in middleware helpers, and use
locked buffers for concurrent log capture.
Header-only modifiers no longer use LazyResponseModifier with buffering
always enabled. They wrap the response with ModifyResponseWriter and
return after invoking the next handler.
Body modifiers still use LazyResponseModifier with canBufferAndModifyResponseBody.
Add a regression test that uses a 64MiB Content-Length with a small body.
Use `bun build --minify --target browser` instead of `bunx uglify-js` in the
minify-js target.
Exclude `internal/go-proxmox/` from the find pipeline so that tree is not
minified.
Introduce reusable `inbound_mtls_profiles` in root config and support
`entrypoint.inbound_mtls_profile` to require client certificates for all
HTTPS traffic on an entrypoint. Profiles can trust the system CA store,
custom PEM CA files, or both, and are compiled into TLS client-auth
pools during entrypoint initialization.
Also add route-scoped `inbound_mtls_profile` support for HTTP-based
routes when no global entrypoint profile is configured. Route-level mTLS
selection is driven by TLS SNI, preserves existing behavior for open and
unmatched hosts, and returns the intended 421 response when secure
requests omit SNI or when Host and SNI resolve to different routes.
Add validation for missing profile references and unsupported non-HTTP
route usage, update config and route documentation/examples, expand
inbound mTLS handshake and routing regression coverage, and bump
`goutils` for HTTPS listener test support.
Sort proxy.* keys by dot depth, then name, before building the tree so
broader paths apply before deeper ones. When a new value would sit on a
node that is already a map, parse it as a YAML object (tabs normalized to
two spaces), deep-merge, and treat an empty string as an empty object.
Return clear errors when a scalar and a nested map disagree.
Drop the preallocated refPrefixes table in favor of refPrefix(n). Add
internal tests for parseLabelObject, mergeLabelMaps, key order, and
flatten; extend export tests for mixed OIDC-style labels and conflicts.
* refactor(docker): extract label parse and flatten helpers
Refactor ParseLabels by moving proxy label application into applyLabel,
descendLabelMap, and setLabelValue so traversal and leaf merge share one path
without labelLoop continues.
Add splitAliasLabel for ExpandWildcard so proxy.* prefix handling stays in one
place and uses CutPrefix/Cut consistently.
Deduplicate flattenMap and flattenMapAny value handling with flattenValue plus
joinLabelKey and stringifyLabelKey for flattened key construction.
* refactor(docker): structured errors for label type clashes
Replace ad hoc fmt.Errorf messages in descendLabelMap, setLabelValue, and
mergeLabelMaps with UnexpectedTypeError so wording is consistent and mapping
vs scalar conflicts stay explicit.
Hoist requireMap in label tests to a shared helper.
Normalize tabs to two spaces in expandYamlWildcard so wildcard YAML matches
the indentation used in the object-merge path.
* refactor(docker): optional UnexpectedTypeError message for merge conflicts
Extend UnexpectedTypeError with an optional Message field; when set, Error()
returns it instead of the default expect-versus-actual formatting.
mergeLabelMaps sets that message when a mapping would merge into an existing
scalar, so the error states the situation instead of only "expect scalar".
Update TestMergeLabelMaps to assert the new wording.
Refresh indirect and direct golang.org/x pins (crypto, net, sys, text,
arch, mod, tools) across the root module, agent, dnsproviders,
socket-proxy, and h2c_test_server.
Advance the goutils submodule and align pseudo-versions for reverseproxy,
websocket, and server; bump workspace replace commits for agent and
internal/dnsproviders.
Update vultr/govultr to v3.30.0 and mattn/go-isatty to v0.0.21 where they
appear in the graph.
Validate GODOXY_LOCAL_API_ADDR before starting the unauthenticated local
API. Loopback listeners still succeed by default; addresses that bind
all interfaces, unspecified IPs, LAN hosts, or non-loopback names need
GODOXY_LOCAL_API_ALLOW_NON_LOOPBACK=true.
When that opt-in is set and the host is not loopback, log a warning so
non-local exposure is obvious. Wire common.LocalAPIAllowNonLoopback from
LOCAL_API_ALLOW_NON_LOOPBACK and document it (with a risk note) in
.env.example.
Add TestValidateLocalAPIAddr for loopback, wildcard, LAN, and hostname
cases with the allow flag on and off.
Drop the `--axios` flag from the `gen-api-types` target and reflow the
`bunx` `swagger-typescript-api generate` arguments for clearer
continuation lines.
Now generated api.ts is fetch API based and no longer rely on axios.
When a path exists but reads as empty or whitespace-only, return nil
without touching dst, matching the no-file case. This avoids
unmarshaler errors on blank files and matches the updated doc comment.
Finish the file API traversal fix by rooting both GET and SET operations at the
actual file-type directory instead of the process working directory. This blocks
`..` escapes from `config/` and `config/middlewares/` while preserving valid
in-root reads and writes.
Also harden the optional unauthenticated local API listener so it only starts on
loopback addresses (`localhost`, `127.0.0.1`, `::1`). This preserves same-host
automation while preventing accidental exposure on wildcard, LAN, bridge, or
public interfaces.
Add regression tests for blocked traversal on GET and SET, valid in-root writes,
and loopback-only local API address validation. Fix an unrelated config test
cleanup panic so the touched package verification can run cleanly.
Constraint: `GODOXY_LOCAL_API_ADDR` is documented for local automation and must remain usable without adding a new auth flow
Constraint: File API behavior must keep valid config/provider/middleware edits working while blocking path escapes
Rejected: Mirror the previous GET `OpenInRoot(".", ...)` approach in SET | still allows escapes from `config/` to sibling paths under the working directory
Rejected: Keep unauthenticated non-loopback local API binds and document the risk | preserves a high-severity pre-auth network exposure
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Treat `LOCAL_API_ADDR` as same-host only; if non-loopback unauthenticated access is ever needed, gate it behind a separately named explicit insecure opt-in
Tested: `go test -count=1 -ldflags='-checklinkname=0' ./internal/api/v1/file -run 'Test(Get|Set)_PathTraversalBlocked' -v`
Tested: `go test -count=1 -ldflags='-checklinkname=0' ./internal/config -run '^TestValidateLocalAPIAddr$|^TestRouteValidateInboundMTLSProfile$' -v`
Tested: `go test -count=1 -ldflags='-checklinkname=0' ./internal/api/... ./internal/config/...`
Not-tested: End-to-end runtime verification of fsnotify reload behavior after a valid in-root provider edit
Update Docker builder images and all staged go.mod `go` lines to 1.26.2 for the
root module, agent, cli, bench_server, h2c_test_server, dnsproviders, and
socket-proxy.
Upgrade coreos/go-oidc, docker/cli, valyala/fasthttp, OpenTelemetry HTTP
instrumentation and SDK, Google Cloud auth and API clients, genproto RPC,
OCI DNS SDK, and pinned goutils/http packages; advance the goutils submodule
pointer.
Bump direct and indirect dependencies across the main module, agent,
dnsproviders package, and socket-proxy: zerolog, lego, validator, Docker CLI,
OpenTelemetry, cloud SDKs, DNS provider clients, and related transitives.
Advance goutils, go-proxmox, and gopsutil submodules; refresh internal
godoxy/agent and dnsproviders pseudo-versions. Extend Moby exclude list for
newer API and client releases to keep older daemon compatibility.
Add exclusion for all paths under /src/* and modify the websocket protocol header to 'vite-hmr' for improved compatibility with development environments.
Implement Signed Double Submit Cookie pattern to prevent CSRF attacks.
Adds CSRF token generation, validation, and middleware for API endpoints.
Safe methods (GET/HEAD/OPTIONS) automatically receive CSRF cookies, while
unsafe methods require X-CSRF-Token header matching the cookie value with
valid HMAC signature. Includes same-origin exemption for login/callback
endpoints to support browser-based authentication flows.
Use os.OpenRoot to restrict file access to the application root,
preventing directory traversal attacks through the file download endpoint.
Also add test to verify path traversal attempts are blocked.
Add a new GitHub Actions workflow for building Docker images with the "compat" tag on the compat branch. Also update the existing nightly workflow to only run on the compat branch instead of all branches.
Synthetic load balancer routes were created with SchemeNone and a zero
proxy port, so the embedded Route logic treated them as excluded routes.
That caused them to be keyed like excluded routes instead of by alias,
which broke HTTP route lookup in reverse proxy load balancer tests.
Override Key and ShouldExclude for synthetic load balancer routes so
they stay addressable through the HTTP route pool while preserving the
existing behavior for normal backend routes.
Also guard addToLoadBalancer against a nil Homepage on an existing
linked route, and update the reverse proxy test to use the in-memory
test entrypoint rather than depending on real listener setup.
- Update golang base image in Dockerfiles from 1.26.0 to 1.26.1
- Upgrade go.mod go version to 1.26.1 in all modules
- Update github.com/yusing/godoxy from v0.26.0 to v0.27.2
- Update gin from v1.11.0 to v1.12.0, docker/cli to v29.3.0, gotify to v2.9.1
- Update golang.org/x/{net,oauth2,sync,time,sys} to latest versions
- Update OpenTelemetry packages to v1.42.0/v0.67.0
- Update OpenJDK and other cloud provider SDKs
- Update goutils submodule to latest commit
Add `relay_proxy_protocol_header` configuration option for TCP routes that enables
forwarding the original client IP address to upstream services via PROXY protocol
v2 headers. This feature is only available for TCP routes and includes validation
to prevent misuse on UDP routes.
- Add RelayProxyProtocolHeader field to Route struct with JSON tag
- Implement writeProxyProtocolHeader in stream package to craft v2 headers
- Update TCPTCPStream to conditionally send PROXY header to upstream
- Add validation ensuring feature is TCP-only
- Include tests for both enabled/disabled states and incoming proxy header relay
Replace the rewrite requirement check with a BodyResponseModifier
marker and treat header and body modifiers separately.
This ensures header/status rewrites still apply when body rewriting is
blocked (for binary, encoded, or chunked responses), while body changes
are skipped safely. It also avoids body reset/close side effects and
returns early on passthrough responses.
Update middleware tests to cover split header/body behavior and themed
middleware body-skip scenarios.
Prevent response modifiers that require body rewriting from running when
the body rewrite gate blocks buffering (for example, chunked transfer
encoding).
Add an explicit `requiresBodyRewrite` capability and implement it for
HTML/theme/error-page modifiers, including bypass delegation.
Also add a regression test to ensure the original response body remains
readable and is not closed prematurely when rewrite is blocked.
This commit fixeds the "http: read on closed response body" with empty page error
happens when body-rewriting middleware (like themed) runs on responses where body rewrite is blocked (e.g. chunked),
then the gate restores an already-closed original body.
Keep only real disk-like mounts (/, /dev/*,
and /mnt/* excluding /mnt/ itself and /mnt/wsl) to avoid noisy or irrelevant entries in
disk metrics.
Normalize disk map keys to use mountpoints for empty/none and /dev/root
devices so usage data remains stable and accessible across environments.
treat lines ending with unquoted `|` or `&` as continued
conditions in `do` block headers so nested blocks parse correctly
across line breaks.
update `on` condition splitting to avoid breaking on newlines that
follow an unescaped trailing pipe, while still respecting quotes,
escapes, and bracket nesting.
add coverage for multiline `|`/`&` continuations in `do` parsing,
`splitAnd`, `parseOn`, and HTTP flow nested block behavior.
* fix(middleware): restore SSE streaming for POST endpoints
Regression introduced in 16935865 (v0.27.0).
Before that commit, LazyResponseModifier only buffered HTML responses and
let everything else pass through via the IsBuffered() early return. The
refactor replaced it with NewResponseModifier which unconditionally buffers
all writes until FlushRelease() fires after the handler returns. That kills
real-time streaming for any SSE endpoint that uses POST.
The existing bypass at ServeHTTP line 193 only fires when the *request*
carries Accept: text/event-stream. That works for browser EventSource (which
always sets that header) but not for programmatic fetch() calls, which set
Content-Type: application/json on the request and only emit
Content-Type: text/event-stream on the *response*.
Fix: introduce ssePassthroughWriter, a thin http.ResponseWriter wrapper that
sits in front of the ResponseModifier. It watches for Content-Type:
text/event-stream in the response headers at the moment WriteHeader or the
first Write is called. Once detected it copies the buffered headers to the
real writer and switches all subsequent writes to pass directly through with
an immediate Flush(), bypassing the ResponseModifier buffer entirely.
Also tighten the Accept header check from == to strings.Contains so that
Accept: text/event-stream, */* is handled correctly.
Reported against Dockhand (https://github.com/Finsys/dockhand) where
container update progress, image pull logs and vulnerability scan output all
stopped streaming after users upgraded to GoDoxy v0.27.0. GET SSE endpoints
(container logs) continued to work because browsers send Accept:
text/event-stream for EventSource connections.
* fix(middleware): make Content-Type SSE check case-insensitive
* refactor(middleware): extract Content-Type into a named constant
* fix(middleware): enhance safe guard to avoid buffering SSE, WS and large bodies
Reverts some changes in 16935865 and apply more rubust handling.
Use a lazy response modifier that buffers only when the response is safe
to mutate. This prevents middleware from intercepting websocket/SSE
streams, encoded payloads, and non-text or oversized responses.
Set a 4MB max buffered size and gate buffering via response headers
(content type, transfer/content encoding, and content length). Skip
mutation when a response is not buffered or mutation setup fails, and
simplify chained response modifiers to operate on the same response.
Also update the goutils submodule for max body limit support.
---------
Co-authored-by: yusing <yusing.wys@gmail.com>
- Add script logic to create empty placeholder files for minified JS files
so go vet won't complain about missing files
- Run go mod tidy in root and agent directory to clean up dependencies
Add `set -euo pipefail` for strict error handling, check for clean working tree before running, and add trap for cleanup. Move sed replacements from patch file to actual changed Go files to correctly apply sonic-to-json transformations after checkout.
Introduces a new `$redacted` dynamic variable that wraps its single
argument with `strutils.Redact`, allowing sensitive values (e.g.,
authorization headers, query parameters) to be masked in rule
expressions.
The variable accepts exactly one argument, which may itself be a nested
dynamic variable expression such as `$header(Authorization)` or
`$arg(token)`, enabling patterns like `$redacted($header(Authorization))`.
Adds corresponding tests covering plain string redaction, nested header
and query arg wrapping, and the error case when no argument is provided.