Every CVE Search/ Get API key

CVE-2026-22257

Salvo is vulnerable to stored XSS in the list_html function by uploading files with malicious names

8.8 HighFix available

Salvo is a Rust web backend framework. Prior to version 0.88.1, the function list_html generates a file view of a folder without sanitizing the files or folders names, this may potentially lead to XSS in cases where a website allow the access to public files using this feature and anyone can upload a file. This issue has been patched in version 0.88.1.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
High
Integrity
Low
Availability
Low

Exploit likelihood

0.33%Up 0.3 points since Jan 9, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.8

Salvo is vulnerable to stored XSS in the list_html function by uploading files with malicious names

Salvo is a Rust web backend framework. Prior to version 0.88.1, the function list_html generates a file view of a folder without sanitizing the files or folders names, this may potentially lead to XSS in cases where a website allow the access to public files using this feature and anyone can upload a file. This issue has been patched in version 0.88.1.

Record
CVE-2026-22257
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Weakness
CWE-79
Updated
2026-11-02
EUVDEUVD-2026-1423 8.8

Salvo is a Rust web backend framework. Prior to version 0.88.1, the function list_html generates a file view of a folder without sanitizing the files or folders names, this may potentially lead to XSS in cases where a website allow the access to public files using this feature and anyone can upload a file. This issue has been patched in version 0.88.1.

Record
EUVD-2026-1423
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Updated
2026-11-02
GitHub AdvisoriesGHSA-54m3-5fxr-2f3j 8.8

Salvo is vulnerable to stored XSS in the list_html function by uploading files with malicious names

Summary

The function list_html generates a file view of a folder without sanitizing the files or folders names, potentially leading to XSS in cases where a website allows access to public files using this feature, allowing anyone to upload a file.

Details

The vulnerable snippet of code is the following: dir.rs

// ... fn list_html(...
        let mut link = "".to_owned();
        format!(
            r#"<a href="/">{}</a>{}"#,
            HOME_ICON,
            segments
                .map(|seg| {
                    link = format!("{link}/{seg}");
                    format!("/<a href=\"{link}\">{seg}</a>")
                })
                .collect::<Vec<_>>()
                .join("")
        )
// ...

PoC

https://github.com/user-attachments/assets/1e161e17-f033-4cc4-855b-43fd38ed1be4

Here is the example app we used:

mian.rs

use salvo::prelude::*;
use salvo::serve_static::StaticDir;
use std::path::PathBuf;
use tokio::fs;

const INDEX_HTML: &str = r#"<!doctype html>
<html>
  <head><meta charset="utf-8"><title>StaticDir PoC</title></head>
  <body>
    <h2>Upload a file</h2>
    <form action="/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="file" />
      <button type="submit">Upload</button>
    </form>

    <p>Browse uploads:</p>
    <ul>
      <li><a href="/files">/files</a></li>
      <li><a href="/files/">/files/</a></li>
    </ul>
  </body>
</html>
"#;

#[handler]
async fn index(res: &mut Response) {
    res.render(Text::Html(INDEX_HTML));
}

#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
    fs::create_dir_all("uploads").await.expect("create uploads dir");

    let form = match req.form_data().await {
        Ok(v) => v,
        Err(e) => {
            res.status_code(StatusCode::BAD_REQUEST);
            res.render(Text::Plain(format!("form_data parse failed: {e}")));
            return;
        }
    };

    let Some(file_part) = form.files.get("file") else {
        res.status_code(StatusCode::BAD_REQUEST);
        res.render(Text::Plain("missing file field (name=\"file\")"));
        return;
    };

    let original_name = file_part.name().unwrap_or("upload.bin");

    let mut dest = PathBuf::from("uploads");
    dest.push(original_name);

    let tmp_path = file_part.path();
    if let Err(e) = fs::copy(tmp_path, &dest).await {
        res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
        res.render(Text::Plain(format!("save failed: {e}")));
        return;
    }

    res.render(Text::Plain(format!(
        "Uploaded as: {original_name}\nNow open: http://127.0.0.1:5800/files/\n"
    )));
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();
    fs::create_dir_all("uploads").await.expect("create uploads dir");

    let router = Router::new()
        .get(index)
        .push(Router::with_path("upload").post(upload))
        .push(
            Router::with_path("files/{**rest_path}")
                .get(StaticDir::new("uploads").auto_list(true)),
        );

    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}

Cargo.toml

[package]
name = "poc"
version = "0.1.0"
edition = "2024"

[dependencies]
salvo = { version = "0.85.0", features = ["serve-static"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
tracing-subscriber = "0.3"

Impact

JavaScript execution, most likely leading to an account takeover, depending on the site's constraint (CSP, etc…).

Record
GHSA-54m3-5fxr-2f3j
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Weakness
CWE-79
Updated
2026-11-02
NVD 8.8

Salvo is a Rust web backend framework. Prior to version 0.88.1, the function list_html generates a file view of a folder without sanitizing the files or folders names, this may potentially lead to XSS in cases where a website allow the access to public files using this feature and anyone can upload a file. This issue has been patched in version 0.88.1.

Record
CVE-2026-22257
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Weakness
CWE-79
Updated
2026-11-02
OSV 8.8

Salvo is vulnerable to stored XSS in the list_html function by uploading files with malicious names

Salvo is a Rust web backend framework. Prior to version 0.88.1, the function list_html generates a file view of a folder without sanitizing the files or folders names, this may potentially lead to XSS in cases where a website allow the access to public files using this feature and anyone can upload a file. This issue has been patched in version 0.88.1.

Record
CVE-2026-22257
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Weakness
CWE-79
Updated
2026-11-02
OSVGHSA-54m3-5fxr-2f3j 8.8

Salvo is vulnerable to stored XSS in the list_html function by uploading files with malicious names

Summary

The function list_html generates a file view of a folder without sanitizing the files or folders names, potentially leading to XSS in cases where a website allows access to public files using this feature, allowing anyone to upload a file.

Details

The vulnerable snippet of code is the following: dir.rs

// ... fn list_html(...
        let mut link = "".to_owned();
        format!(
            r#"<a href="/">{}</a>{}"#,
            HOME_ICON,
            segments
                .map(|seg| {
                    link = format!("{link}/{seg}");
                    format!("/<a href=\"{link}\">{seg}</a>")
                })
                .collect::<Vec<_>>()
                .join("")
        )
// ...

PoC

https://github.com/user-attachments/assets/1e161e17-f033-4cc4-855b-43fd38ed1be4

Here is the example app we used:

mian.rs

use salvo::prelude::*;
use salvo::serve_static::StaticDir;
use std::path::PathBuf;
use tokio::fs;

const INDEX_HTML: &str = r#"<!doctype html>
<html>
  <head><meta charset="utf-8"><title>StaticDir PoC</title></head>
  <body>
    <h2>Upload a file</h2>
    <form action="/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="file" />
      <button type="submit">Upload</button>
    </form>

    <p>Browse uploads:</p>
    <ul>
      <li><a href="/files">/files</a></li>
      <li><a href="/files/">/files/</a></li>
    </ul>
  </body>
</html>
"#;

#[handler]
async fn index(res: &mut Response) {
    res.render(Text::Html(INDEX_HTML));
}

#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
    fs::create_dir_all("uploads").await.expect("create uploads dir");

    let form = match req.form_data().await {
        Ok(v) => v,
        Err(e) => {
            res.status_code(StatusCode::BAD_REQUEST);
            res.render(Text::Plain(format!("form_data parse failed: {e}")));
            return;
        }
    };

    let Some(file_part) = form.files.get("file") else {
        res.status_code(StatusCode::BAD_REQUEST);
        res.render(Text::Plain("missing file field (name=\"file\")"));
        return;
    };

    let original_name = file_part.name().unwrap_or("upload.bin");

    let mut dest = PathBuf::from("uploads");
    dest.push(original_name);

    let tmp_path = file_part.path();
    if let Err(e) = fs::copy(tmp_path, &dest).await {
        res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
        res.render(Text::Plain(format!("save failed: {e}")));
        return;
    }

    res.render(Text::Plain(format!(
        "Uploaded as: {original_name}\nNow open: http://127.0.0.1:5800/files/\n"
    )));
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();
    fs::create_dir_all("uploads").await.expect("create uploads dir");

    let router = Router::new()
        .get(index)
        .push(Router::with_path("upload").post(upload))
        .push(
            Router::with_path("files/{**rest_path}")
                .get(StaticDir::new("uploads").auto_list(true)),
        );

    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}

Cargo.toml

[package]
name = "poc"
version = "0.1.0"
edition = "2024"

[dependencies]
salvo = { version = "0.85.0", features = ["serve-static"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"] }
tracing-subscriber = "0.3"

Impact

JavaScript execution, most likely leading to an account takeover, depending on the site's constraint (CSP, etc…).

Record
GHSA-54m3-5fxr-2f3j
CVSS 3.1
8.8 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L
Weakness
CWE-79
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
salvo
crates.io
before 0.88.1
0.88.1

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-22257 is a high vulnerability (CVSS 8.8) in Salvo, published January 8, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.33%, the 26th percentile. Fixed in salvo 0.88.1.

References

MoveEnter Open