CVE-2026-40189
Description
goshs is a SimpleHTTPServer written in Go. Prior to 2.0.0-beta.4, goshs enforces the documented per-folder .goshs ACL/basic-auth mechanism for directory listings and file reads, but it does not enforce the same authorization checks for state-changing routes. An unauthenticated attacker can upload files with PUT, upload files with multipart POST /upload, create directories with ?mkdir, and delete files with ?delete inside a .goshs-protected directory. By deleting the .goshs file itself, the attacker can remove the folder's auth policy and then access previously protected content without credentials. This results in a critical authorization bypass affecting confidentiality, integrity, and availability. This vulnerability is fixed in 2.0.0-beta.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/patrickhener/goshsGo | <= 1.1.4 | — |
Affected products
4Patches
1f212c4f4a126Fix security issue reported at: https://github.com/patrickhener/goshs/security/advisories/GHSA-wvhv-qcqf-f3cx. Also make .goshs auth work recursive.
3 files changed · +92 −4
httpserver/filebased.go+31 −0 modified@@ -48,3 +48,34 @@ func (fs *FileServer) findSpecialFile(folder string) (configFile, error) { return config, nil } + +// findEffectiveACL walks up the directory tree from dir toward the webroot, +// returning the nearest configFile found. This allows a .goshs placed in a +// parent directory to apply recursively to all subdirectories without +// leaking upward past the webroot. +func (fs *FileServer) findEffectiveACL(dir string) (configFile, error) { + webroot := filepath.Clean(fs.Webroot) + current := filepath.Clean(dir) + + for { + config, err := fs.findSpecialFile(current) + if err != nil { + return configFile{}, err + } + if config.Auth != "" || len(config.Block) > 0 { + return config, nil + } + // Stop once we have checked the webroot itself + if current == webroot { + break + } + parent := filepath.Dir(current) + if parent == current { + // Reached filesystem root – guard against infinite loop + break + } + current = parent + } + + return configFile{}, nil +}
httpserver/handler.go+30 −4 modified@@ -83,18 +83,18 @@ func (fs *FileServer) doDir(file *os.File, w http.ResponseWriter, req *http.Requ } } - // Check if the dir has a .goshs ACL file - config, err := fs.findSpecialFile(file.Name()) + // Check for effective .goshs ACL (walks up to webroot so parent configs apply recursively) + config, err := fs.findEffectiveACL(file.Name()) if err != nil { logger.Errorf("error reading file based access config: %+v", err) } fs.processDir(w, req, file, upath, json, config) } func (fs *FileServer) doFile(file *os.File, w http.ResponseWriter, req *http.Request) { - // If it is a file we need to check for .goshs one directory up + // Walk up from the file's directory to find the effective .goshs ACL parent := filepath.Dir(file.Name()) - config, err := fs.findSpecialFile(parent) + config, err := fs.findEffectiveACL(parent) if err != nil { logger.Errorf("error reading file based access config: %+v", err) } @@ -685,6 +685,22 @@ func (fs *FileServer) deleteFile(w http.ResponseWriter, req *http.Request) { return } + // Block deletion of the .goshs ACL file itself + if filepath.Base(deletePath) == ".goshs" { + fs.handleError(w, req, fmt.Errorf("cannot delete ACL file"), http.StatusForbidden) + return + } + + // Enforce .goshs ACL (recursive: walks up to webroot) + aclDir := filepath.Dir(deletePath) + acl, aclErr := fs.findEffectiveACL(aclDir) + if aclErr != nil { + logger.Errorf("error reading file based access config: %+v", aclErr) + } + if ok := fs.applyCustomAuth(w, req, acl); !ok { + return + } + err = os.RemoveAll(deletePath) if err != nil { logger.Warnf("error removing %+v", deletePath) @@ -913,6 +929,16 @@ func (fs *FileServer) handleMkdir(w http.ResponseWriter, r *http.Request) { return } + // Enforce .goshs ACL (recursive: walks up to webroot) + parentDir := filepath.Dir(finalPath) + acl, aclErr := fs.findEffectiveACL(parentDir) + if aclErr != nil { + logger.Errorf("error reading file based access config: %+v", aclErr) + } + if ok := fs.applyCustomAuth(w, r, acl); !ok { + return + } + // Create directory err = os.MkdirAll(finalPath, 0755) if err != nil {
httpserver/updown.go+31 −0 modified@@ -27,6 +27,22 @@ func (fs *FileServer) put(w http.ResponseWriter, req *http.Request) { return } + // Block overwriting the .goshs ACL file + if filepath.Base(savepath) == ".goshs" { + fs.handleError(w, req, fmt.Errorf("cannot overwrite ACL file"), http.StatusForbidden) + return + } + + // Enforce .goshs ACL (recursive: walks up to webroot) + targetDir := filepath.Dir(savepath) + acl, aclErr := fs.findEffectiveACL(targetDir) + if aclErr != nil { + logger.Errorf("error reading file based access config: %+v", aclErr) + } + if ok := fs.applyCustomAuth(w, req, acl); !ok { + return + } + body, err := io.ReadAll(req.Body) if err != nil { logger.Errorf("unable to read PUT request body: %+v", err) @@ -73,6 +89,15 @@ func (fs *FileServer) upload(w http.ResponseWriter, req *http.Request) { return } + // Enforce .goshs ACL (recursive: walks up to webroot) + acl, aclErr := fs.findEffectiveACL(targetDir) + if aclErr != nil { + logger.Errorf("error reading file based access config: %+v", aclErr) + } + if ok := fs.applyCustomAuth(w, req, acl); !ok { + return + } + reader, err := req.MultipartReader() if err != nil { logger.Errorf("reading multipart request: %+v", err) @@ -96,6 +121,12 @@ func (fs *FileServer) upload(w http.ResponseWriter, req *http.Request) { filenameSlice := strings.Split(part.FileName(), "/") filenameClean := filenameSlice[len(filenameSlice)-1] + // Block overwriting the .goshs ACL file + if filenameClean == ".goshs" { + logger.Warnf("blocked attempt to upload file named .goshs") + continue + } + // Prepare destination file paths finalPath := filepath.Join(targetDir, filenameClean) tempPath := finalPath + "~"
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/patrickhener/goshs/commit/f212c4f4a126556bab008f79758e21a839ef2c0fnvdPatchWEB
- github.com/patrickhener/goshs/security/advisories/GHSA-wvhv-qcqf-f3cxnvdExploitMitigationVendor AdvisoryWEB
- github.com/advisories/GHSA-wvhv-qcqf-f3cxghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-40189ghsaADVISORY
- github.com/patrickhener/goshs/releases/tag/v2.0.0-beta.4nvdProductRelease NotesWEB
News mentions
0No linked articles in our index yet.