infiniflow/ragflow · critical
checkpoint HMAC key: invalid base64 in CHECKPOINT_HMAC_KEY:
Error message
checkpoint HMAC key: invalid base64 in CHECKPOINT_HMAC_KEY: {err} What it means
loadCheckpointHMACKey runs at package initialization (var checkpointHMACKey = loadCheckpointHMACKey()) and panics when the CHECKPOINT_HMAC_KEY env var is set but is not valid standard base64. The key (32 bytes, base64-encoded) is used to HMAC checkpoint payloads; treating malformed input as a fatal startup error prevents silently running with a bogus key that would break checkpoint integrity verification.
Source
Thrown at internal/harness/core/interrupt.go:228
// ---- Checkpoint integrity (HMAC) ----
const (
hmacLen = 32
envHMACKey = "CHECKPOINT_HMAC_KEY"
)
// checkpointHMACKey reads the HMAC key from the CHECKPOINT_HMAC_KEY env var
// (base64-encoded, 32 bytes). If unset, a random key is generated per startup
// with a log warning — this is safe for single-process in-memory usage but
// will BREAK checkpoint resume across process restarts. Production deployments
// MUST set CHECKPOINT_HMAC_KEY to a stable base64-encoded 32-byte secret.
var checkpointHMACKey = loadCheckpointHMACKey()
func loadCheckpointHMACKey() []byte {
if env := common.GetEnv(envHMACKey); env != "" {
k, err := base64.StdEncoding.DecodeString(env)
if err != nil {
panic("checkpoint HMAC key: invalid base64 in " + envHMACKey + ": " + err.Error())
}
if len(k) != 32 {
panic("checkpoint HMAC key: " + envHMACKey + " must decode to exactly 32 bytes, got " + fmt.Sprintf("%d", len(k)))
}
return k
}
k := make([]byte, 32)
if _, err := rand.Read(k); err != nil {
panic("failed to generate checkpoint HMAC key: " + err.Error())
}
common.Warn("checkpoint HMAC env not set — using random per-process key; checkpoint resume across restarts will fail", zap.String("env", envHMACKey))
return k
}
func computeCheckpointHMAC(payload []byte) []byte {
mac := hmac.New(sha256.New, checkpointHMACKey)
mac.Write(payload)
return mac.Sum(nil)View on GitHub (pinned to 554fb1133a)
Solutions
- Generate a correct value: openssl rand -base64 32 and set CHECKPOINT_HMAC_KEY to its output
- Strip any trailing newline/whitespace: export CHECKPOINT_HMAC_KEY="$(printf %s "$CHECKPOINT_HMAC_KEY")"
- If the secret is urlsafe base64, re-encode it to standard base64 (replace - with +, _ with /) or store it decoded
- Verify with: echo -n "$CHECKPOINT_HMAC_KEY" | base64 -d | wc -c # must print 32
Example fix
# before export CHECKPOINT_HMAC_KEY="a1b2c3..." # raw hex -> panic: invalid base64 # after export CHECKPOINT_HMAC_KEY="$(openssl rand -base64 32)"
Defensive patterns
Strategy: validation
Validate before calling
if v := os.Getenv("CHECKPOINT_HMAC_KEY"); v != "" {
if _, err := base64.StdEncoding.DecodeString(strings.TrimSpace(v)); err != nil {
return fmt.Errorf("CHECKPOINT_HMAC_KEY must be standard base64")
}
} Prevention
- Generate the key only with openssl rand -base64 32 — never hex or urlsafe encoding
- Trim whitespace when loading secrets from files or YAML block scalars
- Add a startup config lint (decode + length check) to deployment pipelines
When it happens
Trigger: Setting CHECKPOINT_HMAC_KEY to a raw (non-base64) 32-byte string, a hex-encoded key, a base64 URL-safe-encoded value using -/_ instead of +//, or a value with trailing whitespace/newline or a typo — any input base64.StdEncoding.DecodeString rejects. The panic fires at process startup, before serving.
Common situations: Operators paste `openssl rand -hex 32` output (hex, not base64) into the env var; a shell here-doc or YAML block scalar appends a newline; a secret manager injects urlsafe-base64; copy-paste drops or mangles trailing '=' padding.
Related errors
- checkpoint HMAC key: CHECKPOINT_HMAC_KEY must decode to exac
- failed to initialize logger: {err}
- Invoke: invalid proxy URL %q: %v
- Invoke: proxy URL %q has no host
- ListOperations: nth requires n to be within the valid range
AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15).
Data as JSON: /api/errors/0fdfb3aca8fd09e2.
Report an issue: GitHub.