Hi, thanks for building the request visualizer. I noticed a privacy hardening issue in the current main branch (02c9c766679e): the monitor stores full request/response content and exposes it through UI/API routes, but I could not find an access boundary around those routes.
Relevant code:
proxy/cmd/proxy/main.go
52 corsHandler := handlers.CORS(
53 handlers.AllowedOrigins([]string{"*"}),
54 handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
55 handlers.AllowedHeaders([]string{"*"}),
56 )
Lines 52-56 allow any origin, method, and header.
65 r.HandleFunc("/", h.UI).Methods("GET")
66 r.HandleFunc("/ui", h.UI).Methods("GET")
67 r.HandleFunc("/api/requests", h.GetRequests).Methods("GET")
68 r.HandleFunc("/api/requests", h.DeleteRequests).Methods("DELETE")
69 r.HandleFunc("/api/conversations", h.GetConversations).Methods("GET")
70 r.HandleFunc("/api/conversations/{id}", h.GetConversationByID).Methods("GET")
71 r.HandleFunc("/api/conversations/project", h.GetConversationsByProject).Methods("GET")
Lines 65-71 expose the dashboard and request-history APIs. I did not find an auth middleware around these routes.
75 srv := &http.Server{
76 Addr: ":" + cfg.Server.Port,
77 Handler: corsHandler(r),
Lines 75-77 bind the HTTP server to all interfaces on the configured port.
proxy/internal/handler/handlers.go
78 requestLog := &model.RequestLog{
79 RequestID: requestID,
80 Timestamp: time.Now().Format(time.RFC3339),
81 Method: r.Method,
82 Endpoint: r.URL.Path,
83 Headers: SanitizeHeaders(r.Header),
84 Body: req,
85 Model: decision.OriginalModel,
86 OriginalModel: decision.OriginalModel,
87 RoutedModel: decision.TargetModel,
Lines 78-87 save sanitized headers, the full request body, and routing metadata.
183 allRequests, err := h.storageService.GetAllRequests(modelFilter)
...
213 w.Header().Set("Content-Type", "application/json")
214 json.NewEncoder(w).Encode(struct {
215 Requests []model.RequestLog `json:"requests"`
216 Total int `json:"total"`
217 }{
218 Requests: requests,
219 Total: total,
220 })
Lines 183 and 213-220 fetch stored requests and return them as JSON.
274 var fullResponseText strings.Builder
276 var streamingChunks []string
...
282 scanner := bufio.NewScanner(resp.Body)
283 for scanner.Scan() {
284 line := scanner.Text()
285 if line == "" || !strings.HasPrefix(line, "data:") {
286 continue
287 }
289 streamingChunks = append(streamingChunks, line)
290 fmt.Fprintf(w, "%s\n\n", line)
Lines 274-290 both forward streaming chunks to the client and keep a copy in memory for logging.
373 responseLog := &model.ResponseLog{
374 StatusCode: resp.StatusCode,
375 Headers: SanitizeHeaders(resp.Header),
376 StreamingChunks: streamingChunks,
...
413 responseLog.Body = json.RawMessage(responseBodyBytes)
415 requestLog.Response = responseLog
416 if err := h.storageService.UpdateRequestWithResponse(requestLog); err != nil {
Lines 373-416 persist streaming chunks and a reconstructed response body.
427 func (h *Handler) handleNonStreamingResponse(...)
428 responseBytes, err := io.ReadAll(resp.Body)
...
448 responseLog.Body = json.RawMessage(responseBytes)
...
453 responseLog.BodyText = string(responseBytes)
...
460 requestLog.Response = responseLog
461 if err := h.storageService.UpdateRequestWithResponse(requestLog); err != nil {
Lines 427-461 persist non-streaming responses.
config.yaml.example:41-44 documents the persistent SQLite database path as requests.db.
README.md:19 describes SQLite-based logging of all API interactions.
README.md:295-297 documents searchable request history and request/response body inspection.
Why this matters:
The UI is useful, but these records can include user prompts, source code, tool results, model routing decisions, and assistant responses. If someone runs the proxy in Docker, on a server, or on a LAN, anyone who can reach the port can read /api/requests or the dashboard. They can also clear the history through DELETE /api/requests.
Suggested direction:
- Add an optional monitor/API token, enabled by default for non-local binds.
- Alternatively, add a
monitor.enabled or monitor_auth_token setting and require it before exposing stored request/response bodies.
- Bind to
127.0.0.1 by default, or add a local-only guard unless the user explicitly configures public listening.
- Keep the current header sanitization. The missing boundary is around stored bodies and responses rather than header redaction.
Hi, thanks for building the request visualizer. I noticed a privacy hardening issue in the current main branch (
02c9c766679e): the monitor stores full request/response content and exposes it through UI/API routes, but I could not find an access boundary around those routes.Relevant code:
proxy/cmd/proxy/main.goLines 52-56 allow any origin, method, and header.
Lines 65-71 expose the dashboard and request-history APIs. I did not find an auth middleware around these routes.
Lines 75-77 bind the HTTP server to all interfaces on the configured port.
proxy/internal/handler/handlers.goLines 78-87 save sanitized headers, the full request body, and routing metadata.
Lines 183 and 213-220 fetch stored requests and return them as JSON.
Lines 274-290 both forward streaming chunks to the client and keep a copy in memory for logging.
Lines 373-416 persist streaming chunks and a reconstructed response body.
Lines 427-461 persist non-streaming responses.
config.yaml.example:41-44documents the persistent SQLite database path asrequests.db.README.md:19describes SQLite-based logging of all API interactions.README.md:295-297documents searchable request history and request/response body inspection.Why this matters:
The UI is useful, but these records can include user prompts, source code, tool results, model routing decisions, and assistant responses. If someone runs the proxy in Docker, on a server, or on a LAN, anyone who can reach the port can read
/api/requestsor the dashboard. They can also clear the history throughDELETE /api/requests.Suggested direction:
monitor.enabledormonitor_auth_tokensetting and require it before exposing stored request/response bodies.127.0.0.1by default, or add a local-only guard unless the user explicitly configures public listening.