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

  1. Treat it as an internal invariant failure: report it, do not retry
  2. If you vendored or forked pkg/credential, verify keyLen is still 32 and the x/crypto version matches go.mod
  3. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/d119b9c4bfd680f2. Report an issue: GitHub.