Skip to content

fix(deps): update rust crate salvo to 0.89.0 [security]#122

Open
renovate[bot] wants to merge 1 commit intomainfrom
renovate/crate-salvo-vulnerability
Open

fix(deps): update rust crate salvo to 0.89.0 [security]#122
renovate[bot] wants to merge 1 commit intomainfrom
renovate/crate-salvo-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate Bot commented Jan 8, 2026

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Type Update Change
salvo (source) dependencies minor 0.37.90.89.0

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

CVE-2026-22257 / GHSA-54m3-5fxr-2f3j

More information

Details

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
POC1.mp4

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…).

Severity

  • CVSS Score: 8.8 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Salvo is vulnerable to reflected XSS in the list_html function

CVE-2026-22256 / GHSA-rjf8-2wcw-f6mp

More information

Details

Summary

The function list_html generates an file view of a folder which includes a render of the current path, in which its inserted in the HTML without proper sanitation, leading to reflected XSS. The request path is decoded and normalized in the matching stage but is not inserted raw in the HTML view (current.path). The only constraint here is for the root path (e.g., /files in the PoC example) to have a subdirectory (e. g., common ones like styles/scripts/etc.) so that the matching returns the list HTML page instead of the Not Found page.

Details

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

// ... fn list_html(...
    let mut ftxt = format!(
        r#"<!DOCTYPE html><html><head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width">
        <title>{}</title>
        <style>{}</style></head><body><header><h3>Index of: {}</h3></header><hr/>"#,
        current.path,
        HTML_STYLE,
        header_links(&current.path)
    );
// ...

As seen here <title>{}</title> it is inserted unsafely.

PoC
salvo_poc.mp4

Here is the example app, note this doesn’t need an upload feature (e.g to the other reported vulnerability), only the sub-folder is required.

main.rs

use salvo::prelude::*;
use salvo::serve_static::StaticDir;
use tokio::fs;

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

    let router = Router::new()
        .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 = "salvo-staticdir-xss-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"

Setup commands:

mkdir uploads
mkdir uploads/bla
Impact

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

Severity

  • CVSS Score: 8.8 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Salvo Affected by Denial of Service via Unbounded Memory Allocation in Form Data Parsing

CVE-2026-33241 / GHSA-pp9r-xg4c-8j4x

More information

Details

Summary

Salvo's form data parsing implementations (form_data() method and Extractible macro) do not enforce payload size limits before reading request bodies into memory. This allows attackers to cause Out-of-Memory (OOM) conditions by sending extremely large payloads, leading to service crashes and denial of service.

Details
Vulnerability Description

Three attack vectors exist in Salvo's form handling:

  1. URL-encoded form data (application/x-www-form-urlencoded)

    • Request::form_data() calls BodyExt::collect(body) which reads the entire body into memory without size checking
    • Affects handlers using req.form_data().await directly
  2. Multipart form data (multipart/form-data)

    • Similar unbounded memory allocation during parsing
    • Affects handlers processing multipart uploads
  3. Extractible macro

    • #[derive(Extractible)] with #[salvo(extract(default_source(from = "body")))] internally calls form_data()
    • Vulnerabilities propagate to all extractors using body sources
Root Cause

The FormData::read() implementation prioritizes convenience over safety by reading entire request bodies before validation. Even when Request::payload_with_max_size() is available, it's not automatically applied in the form parsing path.

PoC
  1. run Extract data from request example in readme.md in docker file with limited memory say 100mb.
  2. Send application/x-www-form-urlencoded OR multipart/form-data payload to the endpoint.
  3. The server process OOM-crashes, instead of returning 413 error.
Impact
Immediate Effects
  • Service Unavailability: Servers crash under memory pressure
  • Resource Exhaustion: Single request can consume all available memory
  • Cascading Failures: In containerized environments, OOM can affect other services
Attack Characteristics
  • Low Cost: Attacker needs minimal bandwidth (header only, body can be streamed)
  • No Authentication: Exploitable on public endpoints
  • Difficult to Rate-Limit: Traditional rate limiting may not prevent single large request
  • Amplification: Small network cost → large memory consumption
Real-World Scenarios
  1. Public API endpoints accepting form data
  2. User registration/profile update handlers
  3. File upload endpoints using multipart forms
  4. Any endpoint using #[derive(Extractible)] with body sources
Suggestion: Make Multipart File Upload Handling Explicit Opt-In
Problem Statement

Currently, Salvo's multipart form data parsing automatically handles file uploads without explicit developer intent. This creates several security and usability concerns:

  1. Unintended File Storage: Developers may unknowingly accept file uploads when they only intended to handle text fields
  2. Disk Space Exhaustion: Automatic file buffering to disk can fill storage without proper limits
  3. Resource Cleanup: Temporary files may not be properly cleaned up if handlers don't expect them
  4. Attack Surface: Endpoints inadvertently become file upload targets

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

salvo-rs/salvo (salvo)

v0.89.3

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.89.2...v0.89.3

v0.89.2

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.89.1...v0.89.2

v0.89.1

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.89.0...v0.89.1

v0.89.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.88.1...v0.89.0

v0.88.1

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.88.0...v0.88.1

v0.88.0

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.87.1...v0.88.0

v0.87.1

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.87.0...v0.87.1

v0.87.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.86.0...v0.87.0

v0.86.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.85.0...v0.86.0

v0.85.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.84.2...v0.85.0

v0.84.2

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.84.1...v0.84.2

v0.84.1

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.84.0...v0.84.1

v0.84.0

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.83.0...v0.84.0

v0.83.0

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.82.0...v0.83.0

v0.82.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.81.0...v0.82.0

v0.81.0

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.80.0...v0.81.0

v0.80.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.79.0...v0.80.0

v0.79.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.78.0...v0.79.0

v0.78.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.77.1...v0.78.0

v0.77.1

Compare Source

What's Changed

Full Changelog: salvo-rs/salvo@v0.77.0...v0.77.1

v0.77.0

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.76.2...v0.77.0

v0.76.2

Compare Source

What's Changed

New Contributors

Full Changelog: salvo-rs/salvo@v0.76.1...v0.76.2

v0.76.1

Compare Source

What's Changed

New Contributors

Full Changelog: <https://github.com/salvo


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot force-pushed the renovate/crate-salvo-vulnerability branch from 45f2a67 to 3ce7b01 Compare February 2, 2026 15:33
@renovate renovate Bot force-pushed the renovate/crate-salvo-vulnerability branch from 3ce7b01 to c9064dc Compare February 12, 2026 17:36
@renovate renovate Bot force-pushed the renovate/crate-salvo-vulnerability branch from c9064dc to c82880a Compare February 25, 2026 11:56
@renovate renovate Bot force-pushed the renovate/crate-salvo-vulnerability branch 2 times, most recently from 921c070 to d7b4935 Compare March 19, 2026 13:23
@renovate renovate Bot changed the title fix(deps): update rust crate salvo to 0.88.0 [security] fix(deps): update rust crate salvo to 0.89.0 [security] Mar 19, 2026
@renovate renovate Bot changed the title fix(deps): update rust crate salvo to 0.89.0 [security] fix(deps): update rust crate salvo to 0.89.0 [security] - autoclosed Mar 27, 2026
@renovate renovate Bot closed this Mar 27, 2026
@renovate renovate Bot deleted the renovate/crate-salvo-vulnerability branch March 27, 2026 01:06
@renovate renovate Bot changed the title fix(deps): update rust crate salvo to 0.89.0 [security] - autoclosed fix(deps): update rust crate salvo to 0.89.0 [security] Mar 30, 2026
@renovate renovate Bot reopened this Mar 30, 2026
@renovate renovate Bot force-pushed the renovate/crate-salvo-vulnerability branch 2 times, most recently from d7b4935 to 8a6a3be Compare March 30, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants