VYPR
High severityNVD Advisory· Published Mar 10, 2026· Updated Apr 6, 2026

CVE-2026-28807

CVE-2026-28807

Description

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in gleam-wisp wisp allows arbitrary file read via percent-encoded path traversal.

The wisp.serve_static function is vulnerable to path traversal because sanitization runs before percent-decoding. The encoded sequence %2e%2e passes through string.replace unchanged, then uri.percent_decode converts it to .., which the OS resolves as directory traversal when the file is read.

An unauthenticated attacker can read any file readable by the application process in a single HTTP request, including application source code, configuration files, secrets, and system files.

This issue affects wisp: from 2.1.1 before 2.2.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wispHex
>= 2.1.1, < 2.2.12.2.1

Affected products

1

Patches

2
161118c43104

v2.2.1

https://github.com/gleam-wisp/wispLouis PilfoldMar 8, 2026via ghsa
4 files changed · +29 15
  • CHANGELOG.md+4 0 modified
    @@ -6,6 +6,10 @@
       package. A `wisp_ewe` package has been created too!
     - `send_file` failing will now result in an internal server error being sent.
     
    +## v2.2.1 - 2026-03-08
    +
    +- Fixed a path traversal attack with encoded URI paths in `serve_static`.
    +
     ## v2.2.0 - 2026-01-16
     
     - The `content_security_policy_protection` middleware has been added.
    
  • gleam.toml+1 1 modified
    @@ -1,5 +1,5 @@
     name = "wisp"
    -version = "2.2.0"
    +version = "2.2.1"
     gleam = ">= 1.11.0"
     description = "A practical web framework for Gleam"
     licences = ["Apache-2.0"]
    
  • src/wisp.gleam+2 5 modified
    @@ -1423,14 +1423,11 @@ pub fn serve_static(
           let path =
             path
             |> string.drop_start(string.length(prefix))
    +        |> uri.percent_decode
    +        |> result.unwrap(path)
             |> string.replace(each: "..", with: "")
             |> filepath.join(directory, _)
     
    -      let path = case uri.percent_decode(path) {
    -        Ok(p) -> p
    -        Error(_) -> path
    -      }
    -
           let file_type =
             req.path
             |> string.split(on: ".")
    
  • test/wisp_test.gleam+22 9 modified
    @@ -503,20 +503,33 @@ pub fn serve_static_not_found_test() {
         |> request.set_path("/stuff/credit_card_details.txt")
       assert {
           use <- wisp.serve_static(request, under: "/stuff", from: "./")
    -      wisp.ok()
    +      wisp.not_found()
         }
    -    == wisp.ok()
    +    == wisp.not_found()
     }
     
    -pub fn serve_static_go_up_test() {
    +pub fn serve_static_path_traversal_attack_test() {
       let request =
         simulate.request(http.Get, "/")
    -    |> request.set_path("/../test/fixture.txt")
    -  assert {
    -      use <- wisp.serve_static(request, under: "/stuff", from: "./src/")
    -      wisp.ok()
    -    }
    -    == wisp.ok()
    +    |> request.set_path("/../gleam.toml")
    +  let response = {
    +    use <- wisp.serve_static(request, under: "/", from: "./src/")
    +    wisp.not_found()
    +  }
    +  assert response == wisp.not_found()
    +    as "the file should not be returned as the .. segment should be discarded to prevent path traversals"
    +}
    +
    +pub fn serve_static_path_traversal_attack_uri_encoded_test() {
    +  let request =
    +    simulate.request(http.Get, "/")
    +    |> request.set_path("/%2e%2e/gleam.toml")
    +  let response = {
    +    use <- wisp.serve_static(request, under: "/", from: "./src/")
    +    wisp.not_found()
    +  }
    +  assert response == wisp.not_found()
    +    as "the file should not be returned as the .. segment should be discarded to prevent path traversals"
     }
     
     pub fn serve_static_etags_returns_304_test() {
    
129dcb1fe10a

Update wisp.gleam

https://github.com/gleam-wisp/wispJarod1980Oct 2, 2025via ghsa
1 file changed · +5 0
  • src/wisp.gleam+5 0 modified
    @@ -1425,6 +1425,11 @@ pub fn serve_static(
             |> string.drop_start(string.length(prefix))
             |> string.replace(each: "..", with: "")
             |> filepath.join(directory, _)
    +      
    +      let path = case uri.percent_decode(path) {
    +        Ok(p) -> p
    +        Error(_) -> path
    +      }
     
           let file_type =
             req.path
    

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

7

News mentions

0

No linked articles in our index yet.