{"record":{"id":"973c8127e5265339","repo":"sipeed/picoclaw","slug":"credential-failed-to-read-credential-file-q-w","errorCode":null,"errorMessage":"credential: failed to read credential file %q: %w","messagePattern":"credential: failed to read credential file %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/credential/credential.go","lineNumber":137,"sourceCode":"\t\t\treturn \"\", fmt.Errorf(\"credential: file:// reference has no filename\")\n\t\t}\n\n\t\tbaseDir := r.resolvedConfigDir\n\t\tif baseDir == \"\" {\n\t\t\tbaseDir = r.configDir\n\t\t}\n\t\tkeyPath := filepath.Join(baseDir, fileName)\n\t\t// Resolve symlinks before enforcing containment to prevent escaping via symlinks.\n\t\trealKeyPath, err := filepath.EvalSymlinks(keyPath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"credential: failed to resolve credential file path %q: %w\", keyPath, err)\n\t\t}\n\t\tif !isWithinDir(realKeyPath, baseDir) {\n\t\t\treturn \"\", fmt.Errorf(\"credential: file:// path escapes config directory\")\n\t\t}\n\t\tdata, err := os.ReadFile(realKeyPath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"credential: failed to read credential file %q: %w\", realKeyPath, err)\n\t\t}\n\n\t\tvalue := strings.TrimSpace(string(data))\n\t\tif value == \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"credential: credential file %q is empty\", realKeyPath)\n\t\t}\n\n\t\treturn value, nil\n\t}\n\n\tif strings.HasPrefix(raw, EncScheme) {\n\t\treturn resolveEncrypted(raw)\n\t}\n\n\t// Plaintext credential — return unchanged.\n\treturn raw, nil\n}\n","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/credential/credential.go#L119-L155","documentation":"Returned by Resolver.Resolve when os.ReadFile fails on the already-symlink-resolved, containment-checked credential path. EvalSymlinks succeeded moments earlier, so the usual cause is a permission problem (file or parent dir not readable by the process) or a race where the file disappeared/was replaced between resolution and read. The real (post-symlink) path is included in the message.","triggerScenarios":"Credential file exists but is mode 0600 owned by another user (service runs as different uid); parent directory loses +x/search permission; file deleted or moved between the EvalSymlinks and ReadFile calls; SELinux/AppArmor denying the read.","commonSituations":"Service user changes (systemd unit hardening, container USER directive) leaving root-owned key files; automated secret rotation briefly unlinking/replacing the file; security modules denying reads from the config location.","solutions":["Check the exact path in the error: `ls -l` it and verify the running user can read it (`sudo -u appuser cat <path>`)","Fix ownership/permissions: chown to the service user or chmod 640 with a shared group; keep 0600 for single-user","If rotation replaces files, rotate atomically (write temp + rename in place) so the path never dangles","Re-run the resolve after fixing to confirm"],"exampleFix":"# before\n-rw------- 1 root root config/openai.key\n\n# after\nchown appuser:appuser config/openai.key && chmod 600 config/openai.key","handlingStrategy":"try-catch","validationCode":"// Preflight readability as the service user before boot.\nif f, err := os.Open(keyPath); err == nil {\n\tf.Close()\n} else {\n\treturn fmt.Errorf(\"credential %s unreadable: %w\", keyPath, err)\n}","typeGuard":null,"tryCatchPattern":"val, err := resolver.Resolve(raw)\nif err != nil {\n\tvar pathErr *fs.PathError\n\tif errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrPermission) {\n\t\t// ownership/permissions fix; do not fall back to plaintext\n\t}\n\treturn \"\", err\n}","preventionTips":["Set 0600 service-user ownership during provisioning; verify in entrypoint","Rotate secrets with write-temp+rename so reads never hit a dangling path","Never downgrade to a plaintext credential on this error"],"tags":["go","credentials","filesystem","permissions","race"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}