fix: dashboard gates, body caps, store ordering, lifecycle#37
Merged
Conversation
Dashboard: gate /api/replay and /api/system-info behind opt-in flags and add optional auth/loopback checks. Replay validates the target IP inside DialContext to close the LookupIP-then-dial TOCTOU and normalizes IPv4-mapped IPv6. Switch system-info env exposure from a substring denylist to an explicit allowlist. Middleware: cap captured request/response bodies at 1 MiB by default, scrub Authorization/Cookie/Set-Cookie/X-Api-Key from stored headers, and forward Flusher/Hijacker/Pusher through the writer. Storage: use crypto/rand for request IDs (timestamp-based IDs collided under load), align in-memory ordering with SQL/Mongo/Redis (newest first), add bson tags so Mongo timestamp sort hits the right field, validate SQL table names, batch cleanup every 32 inserts, and let Store.Add return an error. Drop the package-level signal handler that called os.Exit. Lifetime is now opt-in via WithShutdownContext. Reverts the centralized approach from #28.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sweep through issues that piled up after the React dashboard PR and the
OTEL refactor. Three buckets.
Dashboard surface
Replay was a generic SSRF primitive: anything reachable to the dashboard
could make the host fetch arbitrary URLs (including
169.254.169.254oncloud). It is now off by default, and when enabled the target IP is
validated inside a
DialContextso theLookupIP-then-dial TOCTOU isclosed. IPv4-mapped IPv6 is normalized so
::ffff:10.0.0.1doesn't slippast
IsPrivate.system-infois also off by default. The previous substring denylist(
API,KEY,SECRET, ...) failed open on anything it didn'trecognize —
DATABASE_URL,SLACK_WEBHOOK_URL, etc. Replaced with anexplicit allowlist:
Auth is pluggable:
WithDashboardAuth(fn)for any scheme, plus aWithBasicAuth(user, pass)helper that usessubtle.ConstantTimeCompare.WithLocalhostOnly()for the common ""just debugging"" case.Capture path
WithMaxBodyBytes(n)to change or disableAuthorization,Cookie,Set-Cookie,X-Api-Key,X-Auth-Token,X-Csrf-TokenandProxy-Authorizationare scrubbed before storage.Raw bearer tokens in the dashboard wasn't great.
responseWriterforwardsFlusher,Hijacker,Pusherso SSE,WebSocket upgrades and HTTP/2 push survive the wrap
Storage and lifecycle
crypto/rand(was timestamp-based; collided underload)
backends; ring-buffer eviction is real FIFO instead of random map
iteration
bsontags; the timestamp sort was previously hitting afield that didn't exist
[A-Za-z_][A-Za-z0-9_]*; cleanup is batched every 32 insertsStore.Addreturns an errorThe package-level SIGTERM/
os.Exithandler from #28 is removed. Alibrary has no business killing the host process. Lifetime is now
opt-in:
Testing
go test -race ./...cleango vet ./...cleangolangci-lintfindings vsmain; no new ones introducedstores
Compatibility
Store.Addreturning an error is a breaking change for anyoneimplementing the
Storeinterface directly. The public API(
govisual.Wrap+ the option functions) is unaffected.