JuliusBrussee/caveman · error

CAVEMAN_AUTH_TOKEN must be at least

Error message

CAVEMAN_AUTH_TOKEN must be at least %d bytes

What it means

CAVEMAN_AUTH_TOKEN is set but shorter than the configured minimum (minAuthTokenBytes). Short tokens are brute-forceable, so Load rejects them during startup rather than accepting weak inbound authentication for the standalone BYOK proxy.

Solutions

  1. Generate a longer token, e.g. `openssl rand -hex 32`, and set CAVEMAN_AUTH_TOKEN to it.
  2. Clear the variable entirely (empty string is allowed and means auth disabled) if non-loopback binding is not needed.
  3. If the value comes from a .env file or shell profile, fix the stale short value there and reload the shell.

Example fix

// before
export CAVEMAN_AUTH_TOKEN="abc123"
// after
export CAVEMAN_AUTH_TOKEN="$(openssl rand -hex 32)"
Defensive patterns

Strategy: validation

Validate before calling

token := os.Getenv("CAVEMAN_AUTH_TOKEN")
if token != "" && len(token) < minAuthTokenBytes {
    return fmt.Errorf("CAVEMAN_AUTH_TOKEN must be at least %d bytes", minAuthTokenBytes)
}

Prevention

When it happens

Trigger: Exporting CAVEMAN_AUTH_TOKEN to a string under minAuthTokenBytes bytes (e.g. "abc", "token1") and running config.Load; CI or scripts generating short placeholder secrets.

Common situations: Developer testing locally with a trivial token like "test"; ops checklist leaving a stub value in .env; truncated secret pasted from a secrets manager.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


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

Appendix: source

Thrown at proxy/internal/config/config.go:298

	selector := (&httpproxy.Config{HTTPProxy: raw, HTTPSProxy: raw, NoProxy: env.String("NO_PROXY", env.String("no_proxy", ""))}).ProxyFunc()
	return func(req *http.Request) (*url.URL, error) { return selector(req.URL) }, nil
}

// minAuthTokenBytes is the floor for the inbound shared secret. The token is the
// only gate in front of every configured provider credential once the proxy is
// reachable off-host, so a short one is not a weaker deployment, it is an open one.
const minAuthTokenBytes = 16

// validateAuthToken refuses a token that cannot survive one HTTP header value:
// control bytes terminate the field, and a space would split scheme from value in
// `Authorization: Bearer <token>`. The error never echoes the value — it is a
// secret and this message reaches the proxy log.
func validateAuthToken(token string) error {
	if token == "" {
		return nil
	}
	if len(token) < minAuthTokenBytes {
		return fmt.Errorf("CAVEMAN_AUTH_TOKEN must be at least %d bytes", minAuthTokenBytes)
	}
	for _, r := range token {
		if r == ' ' || r < 0x20 || r == 0x7f {
			return fmt.Errorf("CAVEMAN_AUTH_TOKEN must contain no spaces or control characters")
		}
	}
	return nil
}

// validateListen keeps standalone's BYOK proxy local to one operator unless an
// inbound credential gates it. Binding an empty, wildcard, or non-loopback host
// would expose every configured provider credential to the network with no
// inbound authentication; authenticated says CAVEMAN_AUTH_TOKEN is set, so
// standalone.Auth rejects every request that does not present it and the wider
// bind becomes a deliberate operator choice instead of an accident.
func validateListen(listen string, authenticated bool) error {
	host, port, err := net.SplitHostPort(strings.TrimSpace(listen))
	if err != nil || port == "" {

View on GitHub (pinned to 3ee70a1026)