sipeed/picoclaw · error
credential: HKDF expand failed: %w
Error message
credential: HKDF expand failed: %w
What it means
hkdf.Key(sha256.New, ikm, salt, hkdfInfo, keyLen) failed during key derivation. In golang.org/x/crypto/hkdf, Key only errors when the requested output length exceeds 255 * HashLen (8160 bytes for SHA-256). keyLen is a package constant of 32, so with the current constants this branch is a defensive guard that is effectively unreachable; it exists to fail loudly if someone raises keyLen past the HKDF limit.
Source
Thrown at pkg/credential/credential.go:310
}
if !allowedSSHKeyPath(sshKeyPath) {
return nil, fmt.Errorf(
"credential: SSH key path %q is not in an allowed location (PICOCLAW_SSH_KEY_PATH, PICOCLAW_HOME, or ~/.ssh/)",
sshKeyPath,
)
}
sshBytes, err := os.ReadFile(sshKeyPath)
if err != nil {
return nil, fmt.Errorf("credential: cannot read SSH key %q: %w", sshKeyPath, err)
}
sshHash := sha256.Sum256(sshBytes)
mac := hmac.New(sha256.New, sshHash[:])
mac.Write([]byte(passphrase))
ikm := mac.Sum(nil)
key, err := hkdf.Key(sha256.New, ikm, salt, hkdfInfo, keyLen)
if err != nil {
return nil, fmt.Errorf("credential: HKDF expand failed: %w", err)
}
return key, nil
}
// pickSSHKeyPath returns the SSH private key path to use for encryption/decryption.
//
// Priority:
// 1. override (non-empty explicit argument)
// 2. PICOCLAW_SSH_KEY_PATH env var
// 3. ~/.ssh/picoclaw_ed25519.key (auto-detection)
//
// Returns "" when no key is found; deriveKey will return an error in that case.
func pickSSHKeyPath(override string) string {
if override != "" {
return override
}
if p, ok := os.LookupEnv(SSHKeyPathEnvVar); ok {
return p // respect explicit setting, even if ""View on GitHub (pinned to 49183d7e8d)
Solutions
- Treat it as an internal invariant failure: report it, do not retry
- If you vendored or forked pkg/credential, verify keyLen is still 32 and the x/crypto version matches go.mod
- Run `go mod graph | grep crypto` to confirm the hkdf dependency is the upstream module
Defensive patterns
Strategy: try-catch
Try / catch
if _, err := credential.Encrypt(pass, keyPath, secret); err != nil {
if strings.Contains(err.Error(), "HKDF expand failed") {
// internal invariant: constants or hkdf dependency were modified - file a bug
return fmt.Errorf("internal crypto invariant violated, report upstream: %w", err)
}
return err
} Prevention
- Pin the x/crypto version in go.mod and run `go mod verify` in CI
- Never edit the package's keyLen/hkdfInfo constants without re-deriving the 255*HashLen limit
- Treat this error as a stop-the-line signal, not an operational condition
When it happens
Trigger: No runtime input reaches this branch: ikm, salt, and info are all fixed-size local values. It could only fire if the keyLen constant were edited above 8160 (e.g. someone requesting AES key material far beyond spec) or the hkdf package changed its contract.
Common situations: Practically never seen in production. If it does appear, suspect a forked/vendored copy of the package where keyLen or the hkdf dependency was modified.
Related errors
- credential: keygen: marshal private key: %w
- credential: keygen: marshal public key: %w
- config is nil
- udevadm stdout pipe: %w
- credential: enc:// decryption failed (wrong passphrase or SS
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/d119b9c4bfd680f2.
Report an issue: GitHub.