router-for-me/CLIProxyAPI · error
failed to hash remote management key: %w
Error message
failed to hash remote management key: %w
What it means
If remote-management.secret-key is plaintext (not already a bcrypt hash), the loader hashes it with bcrypt and persists the hash back into the config. This error means bcrypt hashing itself failed — in practice, a secret longer than bcrypt's 72-byte input limit.
Source
Thrown at internal/config/config_load.go:107
}
cfg.CredentialConcurrency = cfg.CredentialConcurrency.WithDefaults()
if errValidate := cfg.CredentialInFlight.Validate(); errValidate != nil {
return nil, errValidate
}
if errValidate := cfg.Codex.LiveMediaRelay.Validate(); errValidate != nil {
return nil, errValidate
}
if errValidate := cfg.ValidateCredentialWeights(); errValidate != nil {
return nil, errValidate
}
// Hash remote management key if plaintext is detected (nested)
// We consider a value to be already hashed if it looks like a bcrypt hash ($2a$, $2b$, or $2y$ prefix).
if cfg.RemoteManagement.SecretKey != "" && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) {
hashed, errHash := hashSecret(cfg.RemoteManagement.SecretKey)
if errHash != nil {
return nil, fmt.Errorf("failed to hash remote management key: %w", errHash)
}
cfg.RemoteManagement.SecretKey = hashed
// Persist the hashed value back to the config file to avoid re-hashing on next startup.
// Preserve YAML comments and ordering; update only the nested key.
_ = SaveConfigPreserveCommentsUpdateNestedScalar(configFile, []string{"remote-management", "secret-key"}, hashed)
}
cfg.RemoteManagement.PanelGitHubRepository = strings.TrimSpace(cfg.RemoteManagement.PanelGitHubRepository)
if cfg.RemoteManagement.PanelGitHubRepository == "" {
cfg.RemoteManagement.PanelGitHubRepository = DefaultPanelGitHubRepository
}
cfg.Pprof.Addr = strings.TrimSpace(cfg.Pprof.Addr)
if cfg.Pprof.Addr == "" {
cfg.Pprof.Addr = DefaultPprofAddr
}
View on GitHub (pinned to 78f0c4079e)
Solutions
- Use a management secret of at most 72 bytes — a 32-byte random value (43-44 base64 chars) is plenty strong.
- Or pre-hash the secret yourself once (htpasswd -bnBC 10 '' 'secret') and put the $2b$... string in the config so the loader skips re-hashing.
- Regenerate the key rather than truncating silently if a long key was distributed.
Example fix
# before (100+ char token, exceeds bcrypt limit) secret-key: 4f8c...very-long-token...9a # after (32-byte base64url secret) secret-key: qUx9m2P7vQ1sKd4LwN8zR3tY6bG5hJ0aXcV2eB4nM7o
Defensive patterns
Strategy: validation
Validate before calling
const maxBcryptBytes = 72
if len(cfg.RemoteManagement.SecretKey) > maxBcryptBytes && !looksLikeBcrypt(cfg.RemoteManagement.SecretKey) {
return fmt.Errorf("secret-key must be <= %d bytes or a pre-computed bcrypt hash", maxBcryptBytes)
} Type guard
func isBcryptHash(s string) bool {
return strings.HasPrefix(s, "$2a$") || strings.HasPrefix(s, "$2b$") || strings.HasPrefix(s, "$2y$")
} Prevention
- Keep management secrets under 72 bytes; 32 bytes of entropy is sufficient.
- If you must inject long tokens, pre-hash them (htpasswd -bnBC 10) and store the $2b$ hash.
- Note the loader rewrites config.yaml with the hash — do not fight it by re-deploying the plaintext each start.
When it happens
Trigger: cfg.RemoteManagement.SecretKey is non-empty, does not start with $2a$/$2b$/$2y$, and hashSecret (bcrypt.GenerateFromPassword) errors — classic cause: a generated API key/token over 72 bytes.
Common situations: Automation injects a long random token (e.g. 96+ char hex/base64) as the management key; pasting a full JWT instead of a short secret.
Related errors
- failed to read config file: %w
- failed to parse config file: %w
- codex.live-media-relay cannot set both allow-private-remote-
- codex.live-media-relay.max-sessions must not be negative
- codex.live-media-relay UDP port minimum and maximum must bot
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/26ac0772561937bf.
Report an issue: GitHub.