{"record":{"id":"5bb13978ab6d43b4","repo":"OpenNHP/opennhp","slug":"fail-to-read-resource-w","errorCode":null,"errorMessage":"fail to read resource: %w","messagePattern":"fail to read resource: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/kbs/resource/resource.go","lineNumber":185,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"fail to get resource absolute path: %w\", err)\n\t}\n\n\t// Check if the path is within the base directory to avoid path traversal attack.\n\tif !strings.HasPrefix(absFullPath, absBaseDir) {\n\t\treturn nil, errors.New(\"invalid resource ID: potential path traversal attack\")\n\t}\n\n\tif _, statErr := os.Stat(absFullPath); statErr != nil {\n\t\tif os.IsNotExist(statErr) {\n\t\t\treturn nil, errors.New(\"resource not found\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"fail to check resource: %w\", statErr)\n\t}\n\n\tdata, err := os.ReadFile(absFullPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"fail to read resource: %w\", err)\n\t}\n\treturn data, nil\n}\n\nfunc encryptWithA256GCM(key, plaintext []byte) (ciphertext, iv, tag []byte, err error) {\n\tblock, err := aes.NewCipher(key)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tiv = make([]byte, gcm.NonceSize())\n\tif _, err = io.ReadFull(rand.Reader, iv); err != nil {\n\t\treturn nil, nil, nil, err","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/kbs/resource/resource.go#L167-L203","documentation":"After os.Stat succeeds, loadResource reads the file with os.ReadFile; any read failure is wrapped as 'fail to read resource: %w'. Stat succeeding but Read failing typically means the file changed between the two calls, or the open/read step hit a permission or I/O problem the stat did not catch. The underlying OS error is preserved for diagnosis.","triggerScenarios":"os.ReadFile(absFullPath) returns an error: file is a directory (stat follows symlinks but ReadFile opens), file removed between Stat and Read, read permission missing (EACCES on open), disk I/O error, or file is a special device that fails on read.","commonSituations":"A directory was placed at a resource path (stat succeeds on directories); resource file deleted concurrently by a redeploy while a request was in flight; permissions allow stat on directory but not read of the file; full disk or failing storage.","solutions":["Check the wrapped errno in the log: EISDIR means the path holds a directory — replace it with the actual resource file.","Re-check and fix file read permissions (chmod a+r / chown to the daemon user).","If the file disappeared mid-request, redeploy the resource and retry; consider reading once and caching.","Verify the file is a regular file (add a Stat mode check before ReadFile).","Check dmesg/system logs for disk I/O errors if errno is EIO."],"exampleFix":"// before\nif _, statErr := os.Stat(absFullPath); statErr != nil { ... }\ndata, err := os.ReadFile(absFullPath)\n// after: validate it is a regular file before reading\nfi, statErr := os.Stat(absFullPath)\nif statErr != nil { ... }\nif !fi.Mode().IsRegular() {\n\treturn nil, errors.New(\"resource not found\")\n}\ndata, err := os.ReadFile(absFullPath)","handlingStrategy":"try-catch","validationCode":"fi, err := os.Stat(absPath)\nif err == nil && !fi.Mode().IsRegular() {\n\t// path is not a regular file; read will fail\n}","typeGuard":"func isReadableRegularFile(path string) bool {\n\tfi, err := os.Stat(path)\n\treturn err == nil && fi.Mode().IsRegular()\n}","tryCatchPattern":"data, err := GetResource(id, token)\nif err != nil {\n\tif errors.Is(err, os.ErrPermission) {\n\t\t// fix file perms / return 403\n\t} else if errors.Is(err, syscall.EISDIR) || errors.Is(err, syscall.ENOTDIR) {\n\t\t// bad deployment: path holds wrong file type\n\t}\n}","preventionTips":["Deploy resources as regular files, never directories.","Use atomic rename (write temp + rename) to avoid reads of half-deleted files.","Set file mode 0644 at deploy time.","Avoid concurrent deletion during serving; use versioned resource dirs.","Monitor for disk errors if EIO appears repeatedly."],"tags":["filesystem","io","kbs","server"],"backgroundTag":"file-read-failed","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}