Skip to content

Default empty API_KEYS makes Docker deployment an open relay for configured auth tokens #21

Description

@chenshj73

Hi, thanks for maintaining this proxy. I noticed a default-deployment safety issue in the current main branch (a1c10357098f): proxy auth is disabled when API_KEYS is empty, while the default listen address and Docker example can expose the service.

Relevant code/docs:

server.go

57 func (s *Server) withMiddleware(next http.Handler) http.Handler {
58     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59         if len(s.cfg.APIKeys) > 0 && !s.authorized(r) {
60             if isClaudeRequestPath(r.URL.Path) {
61                 writeClaudeError(w, http.StatusUnauthorized, "invalid proxy api key", "authentication_error")
62             } else {
63                 writeOpenAIError(w, http.StatusUnauthorized, "invalid proxy api key", "authentication_error", "")
64             }
65             return
66         }
67         next.ServeHTTP(w, r)
68     })
69 }

Line 59 only performs client authentication when APIKeys is non-empty. Line 67 forwards the request when the list is empty.

71 func (s *Server) authorized(r *http.Request) bool {
72     if apiKey := strings.TrimSpace(r.Header.Get("x-api-key")); apiKey != "" {
73         if containsString(s.cfg.APIKeys, apiKey) {
74             return true
75         }
76     }
78     authorization := strings.TrimSpace(r.Header.Get("Authorization"))
...
87     return containsString(s.cfg.APIKeys, apiKey)
88 }

Lines 71-88 implement client-key validation, but that validation is bypassed by the empty-API_KEYS default above.

94 func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
...
100     response := map[string]any{
101         "ok":          true,
102         "started_at":  s.started.UTC(),
103         "uptime_sec":  int(time.Since(s.started).Seconds()),
104         "token_state": s.runs.Snapshots(),
105     }

Lines 100-104 expose operational token-pool state through /healthz under the same auth boundary.

config.go

105 func loadRawConfig(configPath string) (rawConfig, error) {
106     cfg := rawConfig{
107         ListenAddr:       ":8080",
108         UpstreamBaseURL:  "https://www.codebuff.com",
109         RotationInterval: "6h",
110         RequestTimeout:   "15m",
111     }

Line 107 defaults the proxy to listen on :8080, not loopback-only.

README.md

77 | `LISTEN_ADDR` | Proxy listen address (default `:8080`) |
79 | `AUTH_TOKENS` | Freebuff auth tokens (JSON array or comma-separated env var) |
82 | `API_KEYS` | Client API keys for proxy auth (empty = open access) |

The docs explicitly state that empty API_KEYS means open access.

94 docker run -d --name Freebuff2API \
95   -p 8080:8080 \
96   -e AUTH_TOKENS="token1,token2" \
97   ghcr.io/quorinex/freebuff2api:latest

The Docker example publishes the service and configures upstream auth tokens, but does not configure client API_KEYS.

Why this matters:

The proxy holds Freebuff AUTH_TOKENS and turns them into OpenAI-compatible and Anthropic-compatible endpoints. A user following the Docker example on a VPS, NAS, or cloud machine may accidentally expose an unauthenticated relay. Anyone who can reach the port can consume the configured upstream tokens/accounts, increasing waiting-room pressure, quota/rate-limit exhaustion, and account-ban risk.

Suggested direction:

  • Require API_KEYS by default when LISTEN_ADDR is not loopback, or fail startup unless an explicit ALLOW_OPEN_ACCESS=true or --allow-open-access is set.
  • Update the Docker example to include API_KEYS.
  • Print a prominent startup warning when auth is disabled and the listen address is public.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions