Skip to content

fix: critical and major issues from code audit - #254

Merged
umputun merged 10 commits into
masterfrom
fix/critical-major-audit-findings
Jun 1, 2026
Merged

umputun merged 10 commits into
masterfrom
fix/critical-major-audit-findings

Conversation

@umputun

@umputun umputun commented May 30, 2026

Copy link
Copy Markdown
Owner

fixes a set of bugs found by a code audit of the proxy, discovery, and plugin layers. One is a remote-triggerable crash, the rest are resource/lock leaks and a routing dup.

fixed

  • concurrent map write crash (discovery.go). Service.Match runs per request under RLock, but findMatchingMappers lazily wrote mappersCache on the read path. Two parallel requests for the same uncached wildcard/regex host triggered fatal error: concurrent map writes and killed the proxy. Now the cache is guarded by a dedicated cacheLock, and findMatchingMappers is a method on Service. Reachable by an unauthenticated caller whenever wildcard (*.example.com) or regex server patterns are configured.
  • health-ping fd leak (discovery.go). URLMapper.ping never closed the response body, leaking a connection per interval tick. Added defer resp.Body.Close().
  • consul duplicate routes (consulcatalog/client.go). filterServices appended a service once per reproxy.-prefixed tag, so a multi-tagged service produced N duplicate URLMappers and skewed load-balancing. break after the first match.
  • plugin read-lock leak (conductor.go). Conductor.Middleware held RLock across the blocking plugin RPC and returned without RUnlock on the call-error path, deadlocking later plugin (un)register. Now snapshots the alive plugins under the lock and calls RPC outside it.
  • graceful discovery shutdown (main.go). run() launched discovery with context.Background() and never awaited it, so provider-watcher goroutines outlived run() (this surfaced as a flaky -race failure in the logger). Discovery now gets the cancellable ctx and run() waits for it on exit, with a 5s bound so a context-less provider List() can't hang process exit. The signal goroutine and registration no longer leak on early error returns.

docs

  • --remote-lookup-headers now documents that X-Real-IP/X-Forwarded-For are client-supplied and spoofable, so it must only be enabled behind a trusted proxy that overwrites those headers (the per-route IP allowlist trusts them when this is on).

all changes have tests (race test for the crash, deadlock test for the plugin lock, dedup/ping/error-path coverage). Full -race suite green, golangci-lint clean.

one related issue found but left out of scope: a latent data race in proxy.go Http.Run() on the httpServer/httpsServer locals between setup and the ctx.Done() close goroutine. Pre-existing and masked by timing, worth a separate PR.

umputun added 7 commits May 30, 2026 15:25
… return

cancel the discovery context on every run() return path and await a bounded
discoveryDone before exit so provider watcher goroutines don't outlive run.
register signal.Stop and a catch-all defer cancel so the signal goroutine
can't leak across run() calls.
Copilot AI review requested due to automatic review settings May 30, 2026 20:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses audited stability, shutdown, resource leak, routing duplication, and documentation issues across discovery, plugin middleware, Consul discovery, and CLI/docs.

Changes:

  • Adds synchronization and tests for discovery mapper cache access and health ping response cleanup.
  • Fixes plugin middleware lock handling and Consul service deduplication.
  • Propagates shutdown context into discovery/health checks and documents trusted-proxy requirements for remote lookup headers.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Documents spoofing/trust requirements for remote lookup headers.
docs/plans/20260530-fix-critical-major-audit-findings.md Adds implementation plan and verification notes.
app/plugin/conductor.go Snapshots plugins under lock before invoking RPC handlers.
app/plugin/conductor_test.go Adds regression coverage for plugin lock leak.
app/main.go Propagates cancellation to discovery/health checks and updates flag help.
app/main_test.go Adds early-error shutdown regression test.
app/discovery/provider/consulcatalog/client.go Prevents duplicate service names from multiple reproxy tags.
app/discovery/provider/consulcatalog/client_test.go Adds filterServices deduplication tests.
app/discovery/discovery.go Guards mapper cache access and closes ping response bodies.
app/discovery/discovery_test.go Adds concurrent match and ping behavior coverage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/main.go
go func() {
if e := svc.Run(context.Background()); e != nil {
defer close(discoveryDone)
if e := svc.Run(ctx); e != nil && !errors.Is(e, context.Canceled) {
umputun added 3 commits May 30, 2026 18:12
the provider watcher goroutines did a bare send on the events channel, so
on shutdown a producer blocked mid-send (once the consumer stopped on ctx
cancel) could leak. guard both sends with a select on ctx.Done so the
discovery shutdown wait can't be left hanging on a stuck producer.
…e godoc

- add TestConductor_MiddlewareReleasesLockDuringCall: a blocking plugin RPC,
  asserting a write-lock op succeeds while the call is in flight (proves the
  read lock is released before the RPC, not held across it)
- assert run() returns well under the 5s shutdown bound on the error path,
  proving discovery's ctx is cancelled before the wait
- correct Conductor.Middleware godoc: a call error returns HTTP 500 and stops
  the chain, a reply status >= 400 stops with that status
@umputun
umputun merged commit d311e7a into master Jun 1, 2026
5 checks passed
@umputun
umputun deleted the fix/critical-major-audit-findings branch June 1, 2026 03:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants