VYPR
Critical severity9.8NVD Advisory· Published Apr 10, 2026· Updated Apr 14, 2026

CVE-2026-40189

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.

PackageAffected versionsPatched versions
github.com/patrickhener/goshsGo
<= 1.1.4

Affected products

4
  • Goshs/Goshs4 versions
    cpe:2.3:a:goshs:goshs:2.0.0:beta1:*:*:*:go:*:*+ 3 more
    • cpe:2.3:a:goshs:goshs:2.0.0:beta1:*:*:*:go:*:*
    • cpe:2.3:a:goshs:goshs:2.0.0:beta2:*:*:*:go:*:*
    • cpe:2.3:a:goshs:goshs:2.0.0:beta3:*:*:*:go:*:*
    • cpe:2.3:a:goshs:goshs:*:*:*:*:*:go:*:*range: <2.0.0

Patches

1
f212c4f4a126

Fix security issue reported at: https://github.com/patrickhener/goshs/security/advisories/GHSA-wvhv-qcqf-f3cx. Also make .goshs auth work recursive.

https://github.com/patrickhener/goshsPatrick HenerApr 9, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.