fix(security): bound fan-out and method surface and scrub relayed text

- Add a process-wide gate on MCP tools/list backend connections. The
  per-request limit alone let N callers open N times as many.
- Reject CONNECT and TRACE on native passthrough and on MCP and A2A
  targets with 405.
- Scrub URL query strings and the target URL from a backend JSON-RPC
  error message before it reaches the caller.
- Replace the value of redis.address in GET /admin/api/config. The
  users file path stays visible by design.
- Escape line breaks in every log line so an upstream string cannot
  forge log entries.
- Add stripClientAnthropicBeta. When true, POST /v1/messages does not
  forward the client anthropic-beta header. The default stays false.
This commit is contained in:
2026-10-15 22:20:07 +01:00
parent 8c4bd16a22
commit d0d29b8c52
10 changed files with 272 additions and 6 deletions
+13 -3
View File
@@ -4,12 +4,22 @@ import (
"fmt"
"net/http"
"os"
"strings"
)
// logSafe escapes the line breaks in msg. A value an upstream controls, such
// as a panic or error string, must not forge extra log lines.
func logSafe(msg string) string {
if !strings.ContainsAny(msg, "\r\n") {
return msg
}
return strings.NewReplacer("\r", "\\r", "\n", "\\n").Replace(msg)
}
// logf writes an info-level log line to stderr. No timestamps — Traefik
// adds its own. Never log key material.
func (g *Gateway) logf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "llmgw[%s] INFO %s\n", g.name, fmt.Sprintf(format, args...))
fmt.Fprintf(os.Stderr, "llmgw[%s] INFO %s\n", g.name, logSafe(fmt.Sprintf(format, args...)))
}
// warnf writes a warning-level log line to stderr. No timestamps —
@@ -30,7 +40,7 @@ func (g *Gateway) logf(format string, args ...any) {
// see noteConfigWarning's own doc comment for why.
func (g *Gateway) warnf(format string, args ...any) {
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(os.Stderr, "llmgw[%s] WARN %s\n", g.name, msg)
fmt.Fprintf(os.Stderr, "llmgw[%s] WARN %s\n", g.name, logSafe(msg))
g.noteConfigWarning(msg)
}
@@ -80,7 +90,7 @@ func (g *Gateway) configWarningsSnapshot() (warnings []string, dropped int) {
// errorf writes an error-level log line to stderr. No timestamps — Traefik
// adds its own. Never log key material.
func (g *Gateway) errorf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "llmgw[%s] ERROR %s\n", g.name, fmt.Sprintf(format, args...))
fmt.Fprintf(os.Stderr, "llmgw[%s] ERROR %s\n", g.name, logSafe(fmt.Sprintf(format, args...)))
}
// pricingWarn is g's own per-instance pricing-warning sink, passed to