JuliusBrussee/caveman · error

header cannot be forwarded

Error message

header %q cannot be forwarded

What it means

ValidateForwardHeaders rejects any header name that is not a syntactically valid HTTP field name (httpguts.ValidHeaderFieldName) or that begins with the proxy-reserved prefixes x-cave- or x-caveman-. Such headers would let a mount override routing, message framing, or Caveman credentials, so construction fails with "header %q cannot be forwarded".

Solutions

  1. Remove the x-cave-*/x-caveman-* prefixed header from forward_headers — these are proxy-internal.
  2. Fix the header name syntax: valid token characters only, no colons/spaces, non-empty.
  3. Forward a neutral custom name instead if you need a metadata header.
  4. Validate names with httpguts.ValidHeaderFieldName before adding them to config.

Example fix

// before
headers := []string{"X-Caveman-Trace-Id"}
// after
headers := []string{"X-Trace-Id"}
Defensive patterns

Strategy: validation

Validate before calling

func forwardable(name string) bool {
    lower := strings.ToLower(name)
    return httpguts.ValidHeaderFieldName(name) &&
        !strings.HasPrefix(lower, "x-cave-") &&
        !strings.HasPrefix(lower, "x-caveman-")
}

Type guard

func isProxyReservedHeader(name string) bool {
    lower := strings.ToLower(name)
    return strings.HasPrefix(lower, "x-cave-") || strings.HasPrefix(lower, "x-caveman-")
}

Try / catch

if err := openaicompat.ValidateForwardHeaders(headers); err != nil {
    var bad string
    fmt.Sscanf(err.Error(), "header %q", &bad)
    headers = slices.DeleteFunc(headers, func(h string) bool { return h == bad })
}

Prevention

When it happens

Trigger: A name in the forward_headers list fails httpguts.ValidHeaderFieldName (invalid characters, empty) or is prefixed x-cave-/x-caveman- (case-insensitive), evaluated in the first branch of the loop before the denylist switch.

Common situations: Header names with spaces or non-ASCII characters from hand-edited config; attempting to spoof the proxy's own x-caveman-* internal headers; copy-pasted names with trailing colons like "X-Request-Id:".

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/6579a0bc5590c6eb. Report an issue: GitHub.

Appendix: source

Thrown at proxy/providers/openaicompat/openaicompat.go:404

			Provider:      "openai_compatible",
			BaseURL:       baseURL,
			Routes:        []string{prefix + "/"},
			UsageProvider: wireDialectUsageProvider[wireDialect],
		},
		prefix:         prefix,
		forwardHeaders: append([]string(nil), forwardHeaders...),
		wireDialect:    wireDialect,
	}, nil
}

// ValidateForwardHeaders permits explicit provider-specific headers without
// letting a mount override routing, message framing, or Caveman credentials.
// Standard provider authentication is handled by the credential mapper.
func ValidateForwardHeaders(names []string) error {
	for _, name := range names {
		lower := strings.ToLower(name)
		if !httpguts.ValidHeaderFieldName(name) || strings.HasPrefix(lower, "x-cave-") || strings.HasPrefix(lower, "x-caveman-") {
			return fmt.Errorf("header %q cannot be forwarded", name)
		}
		switch lower {
		// Routing/framing, credentials, and fields whose value this proxy
		// constructs. x-forwarded-*/forwarded/x-real-ip would let a caller
		// choose the client address an upstream rate-limits or allowlists on.
		case "host", "connection", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "te", "trailer", "transfer-encoding", "upgrade", "content-length", "expect",
			"authorization", "x-api-key", "api-key", "x-goog-api-key", "x-goog-user-project", "cookie", "set-cookie",
			"forwarded", "x-forwarded-for", "x-forwarded-host", "x-forwarded-proto", "x-forwarded-port", "x-real-ip",
			"user-agent", "content-type":
			return fmt.Errorf("header %q cannot be forwarded", name)
		}
	}
	return nil
}

func ValidateName(name string) error {
	if !validName.MatchString(name) {
		return fmt.Errorf("compat upstream name %q must match [a-z0-9][a-z0-9._-]{0,63}", name)

View on GitHub (pinned to 3ee70a1026)