{"record":{"id":"b71f3a35fc3a069e","repo":"gofr-dev/gofr","slug":"file-does-not-have-read-permission-w","errorCode":null,"errorMessage":"file does not have read permission: %w","messagePattern":"file does not have read permission: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/gofr/http/router.go","lineNumber":38,"sourceCode":"\tDefaultSwaggerFileName       = \"openapi.json\"\n\tstaticServerNotFoundFileName = \"404.html\"\n\tstaticServerIndexFileName    = \"index.html\"\n\n\t// RouterEnvVar selects the route matcher. Unset (or any unrecognized value)\n\t// means MatcherMux, so the default behavior is unchanged.\n\tRouterEnvVar = \"GOFR_ROUTER\"\n\n\t// MatcherMux is gorilla/mux's linear scan — the default.\n\tMatcherMux = \"mux\"\n\t// MatcherTrie is the opt-in segment-trie index, O(path length) in the number\n\t// of registered routes.\n\tMatcherTrie = \"trie\"\n)\n\n// errReadPermissionDenied wraps fs.ErrPermission so that a file whose mode carries no read bit is\n// reported the same way a real EACCES from os.Open is — the two reach respondWithFileError by\n// different routes and must not answer differently.\nvar errReadPermissionDenied = fmt.Errorf(\"file does not have read permission: %w\", fs.ErrPermission)\n\n// Router is responsible for routing HTTP request.\ntype Router struct {\n\tmux.Router\n\tRegisteredRoutes *[]string\n\n\t// useTrie selects the O(path) trie matcher (GOFR_ROUTER=trie) over mux's\n\t// default O(n) linear scan. When false, ServeHTTP delegates to mux exactly\n\t// as before, so the default behavior is byte-for-byte unchanged.\n\tuseTrie bool\n\t// idx is the trie index. It is built once, lazily, on the first request,\n\t// from the routes registered up to that point. This is correct for GoFr's\n\t// lifecycle: every route is registered during startup (app.GET/POST/...,\n\t// the GraphQL route, the static/catch-all handlers) before the server\n\t// accepts its first request, and GoFr does not add routes afterwards. A\n\t// route registered after the first request would not be reflected in the\n\t// trie index — a deliberate trade for a lock-free steady state, matching\n\t// GoFr's static-routing model. buildIdx guards that one-time build.","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/gofr-dev/gofr/blob/187eb24962502e91f1fee856230670958b66e89c/pkg/gofr/http/router.go#L20-L56","documentation":"errReadPermissionDenied wraps fs.ErrPermission and is produced by validateFile in pkg/gofr/http/router.go when a static file matched by the router has a mode with no read bit set. It exists so files lacking the read permission bit are reported identically to a real EACCES from os.Open, ensuring both routes reach respondWithFileError with the same answer.","triggerScenarios":"Serving static files via the router where the requested file's permission mode lacks the read bit (e.g. mode 0200 or 0600 owned by another user), so validateFile rejects it before/instead of the OS open failing.","commonSituations":"Deploying static assets with restrictive umask; files copied by CI as root then served by an unprivileged process; Docker image layers with wrong file ownership/mode.","solutions":["chmod the file so the serving process's user has read permission (e.g. chmod 644 file)","Check ownership with ls -l and chown to the user running the gofr service","Verify the static-file path configured on the router points to readable assets","Run the container/process with a user that has read access to the asset directory"],"exampleFix":"// before (shell)\n-rw-------  1 root root index.html\n// after (shell)\nchmod 644 index.html  # or chown appuser:appuser index.html","handlingStrategy":"validation","validationCode":"info, err := os.Stat(path)\nif err != nil {\n    return fmt.Errorf(\"static file unavailable: %w\", err)\n}\nif info.Mode().Perm()&0o400 == 0 {\n    return fmt.Errorf(\"file lacks read permission: %s\", path)\n}","typeGuard":"func isReadPermissionDenied(err error) bool {\n    return errors.Is(err, fs.ErrPermission)\n}","tryCatchPattern":"if err := serveFile(w, r, path); err != nil {\n    if errors.Is(err, fs.ErrPermission) {\n        http.Error(w, \"forbidden\", http.StatusForbidden)\n        return\n    }\n    http.Error(w, \"not found\", http.StatusNotFound)\n}","preventionTips":["chmod 644 static assets in CI before packaging images","Run the service as a user with read access to asset directories","Add a startup check that walks static dirs and verifies readability","Avoid running containers as root then switching to unprivileged users without chown"],"tags":["filesystem","permissions","static-files"],"backgroundTag":"file-read-permission-denied","analyzedSha":"187eb24962502e91f1fee856230670958b66e89c","analyzedAt":"2026-09-01T20:34:54.554Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}