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
- Check the host entropy source (getrandom availability, /dev/urandom permissions)
- Relax the container seccomp/apparmor profile blocking getrandom(2)
- 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
- Never substitute a predictable RNG on salt failure — fail the request
- Verify getrandom(2) is permitted in container seccomp profiles
- Alert on this error: it indicates a broken host, not a bug
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
- invalid oauth client secret: %w
- invalid oauth access token: %w
- failed to generate API key
- failed to parse oauth client secret
- marshalling machine key: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/ec0a12c17a4050c9.
Report an issue: GitHub.