{"record":{"id":"d119b9c4bfd680f2","repo":"sipeed/picoclaw","slug":"credential-hkdf-expand-failed-w","errorCode":null,"errorMessage":"credential: HKDF expand failed: %w","messagePattern":"credential: HKDF expand failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":310,"sourceCode":"\t}\n\tif !allowedSSHKeyPath(sshKeyPath) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"credential: SSH key path %q is not in an allowed location (PICOCLAW_SSH_KEY_PATH, PICOCLAW_HOME, or ~/.ssh/)\",\n\t\t\tsshKeyPath,\n\t\t)\n\t}\n\tsshBytes, err := os.ReadFile(sshKeyPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"credential: cannot read SSH key %q: %w\", sshKeyPath, err)\n\t}\n\tsshHash := sha256.Sum256(sshBytes)\n\tmac := hmac.New(sha256.New, sshHash[:])\n\tmac.Write([]byte(passphrase))\n\tikm := mac.Sum(nil)\n\n\tkey, err := hkdf.Key(sha256.New, ikm, salt, hkdfInfo, keyLen)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"credential: HKDF expand failed: %w\", err)\n\t}\n\treturn key, nil\n}\n\n// pickSSHKeyPath returns the SSH private key path to use for encryption/decryption.\n//\n// Priority:\n//  1. override (non-empty explicit argument)\n//  2. PICOCLAW_SSH_KEY_PATH env var\n//  3. ~/.ssh/picoclaw_ed25519.key (auto-detection)\n//\n// Returns \"\" when no key is found; deriveKey will return an error in that case.\nfunc pickSSHKeyPath(override string) string {\n\tif override != \"\" {\n\t\treturn override\n\t}\n\tif p, ok := os.LookupEnv(SSHKeyPathEnvVar); ok {\n\t\treturn p // respect explicit setting, even if \"\"","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L292-L328","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := credential.Encrypt(pass, keyPath, secret); err != nil {\n    if strings.Contains(err.Error(), \"HKDF expand failed\") {\n        // internal invariant: constants or hkdf dependency were modified - file a bug\n        return fmt.Errorf(\"internal crypto invariant violated, report upstream: %w\", err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["crypto","hkdf","internal","defensive","unreachable"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}