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

  1. Use a management secret of at most 72 bytes — a 32-byte random value (43-44 base64 chars) is plenty strong.
  2. 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.
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/26ac0772561937bf. Report an issue: GitHub.