juanfont/headscale · critical

generating salt: %w

Error message

generating salt: %w

What it means

hashSecret generates the Argon2id salt with crypto/rand.Read before deriving the key. This error means the OS entropy source failed — on Linux, getrandom(2) virtually never fails after boot, so seeing this indicates a serious environment problem, not an application bug. No credential is persisted when it fires.

Source

Thrown at hscontrol/db/oauth.go:75

	argon2KeyLen  = 32
	argon2SaltLen = 16
)

// argon2Limiter bounds concurrent Argon2id computations. Each costs ~19 MiB and
// the unauthenticated OAuth token endpoint runs one per attempt, so an unbounded
// flood could exhaust memory. ponytail: a global semaphore sized to GOMAXPROCS;
// revisit only if credential hashing ever becomes a throughput bottleneck.
var argon2Limiter = make(chan struct{}, max(2, runtime.GOMAXPROCS(0)))

// hashSecret hashes a credential secret with Argon2id, encoded in PHC string
// form so the parameters travel with the hash. Argon2id is the current OWASP
// recommendation, replacing bcrypt for new credential storage.
func hashSecret(secret string) ([]byte, error) {
	salt := make([]byte, argon2SaltLen)

	_, err := rand.Read(salt)
	if err != nil {
		return nil, fmt.Errorf("generating salt: %w", err)
	}

	hash := argon2.IDKey([]byte(secret), salt, argon2Time, argon2Memory, argon2Threads, argon2KeyLen)

	encoded := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
		argon2.Version, argon2Memory, argon2Time, argon2Threads,
		base64.RawStdEncoding.EncodeToString(salt),
		base64.RawStdEncoding.EncodeToString(hash),
	)

	return []byte(encoded), nil
}

// verifySecret reports whether secret matches a hashSecret-encoded hash. It
// reads the cost parameters from the stored hash and compares in constant time
// so a mismatch leaks no timing signal.
func verifySecret(encoded []byte, secret string) error {
	parts := strings.Split(string(encoded), "$")

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check the host entropy source (getrandom availability, /dev/urandom permissions)
  2. Relax the container seccomp/apparmor profile blocking getrandom(2)
  3. Fail the credential-creation request; do not fall back to a weaker RNG
Defensive patterns

Strategy: try-catch

Try / catch

if _, client, err := hsdb.CreateOAuthClient(...); err != nil {
	if strings.Contains(err.Error(), "generating salt") {
		// host entropy problem; fail closed, alert ops
	}
	return err
}

Prevention

When it happens

Trigger: Early-boot entropy exhaustion on minimal VMs/containers; a broken /dev/urandom mount or seccomp/filter blocking getrandom in exotic sandboxes; file-descriptor exhaustion in older Go runtimes.

Common situations: Tiny containers with restrictive syscall filters; embedded devices booted straight into the service.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/ec0a12c17a4050c9. Report an issue: GitHub.