Skip to content

axum-extra: Content-Disposition parameter injection via unescaped filename in Attachment/FileStream #3646

@shblue21

Description

@shblue21

First, thank you for your continued work on axum and axum-extra.

  • I have looked for existing issues (including closed) about this

Bug Report

Version

  • axum v0.8.8
  • axum-core v0.5.5
  • axum-extra v0.12.3

Platform

Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6020 arm64

Crates

  • axum-extra (response::Attachment)
  • possibly similar behavior in axum-extra::response::file_stream::FileStream

Description

Thanks for maintaining axum/axum-extra.

I may be misunderstanding the intended contract of Attachment::filename(...), but I found behavior that looks surprising from a header-safety perspective.

I tried this code:

use axum::{extract::Path, routing::get, Router};
use axum_extra::response::Attachment;

async fn download(Path(name): Path<String>) -> Attachment<&'static str> {
    Attachment::new("header-check path: /download\n").filename(name)
}

let app = Router::new().route("/download/{name}", get(download));

Request:
curl -i "http://127.0.0.1:4000/download/evil%22%3B%20filename*%3DUTF-8''pwned.txt%3B%20x%3D%22"

Observed:
content-disposition: attachment; filename="evil"; filename*=UTF-8''pwned.txt; x=""

Image Image Image

Expected:
The input should remain inside filename="..." and should not allow adding extra disposition parameters.
This appears related to direct quoted-string construction in:

  • axum-extra/src/response/attachment.rs:90-92
    • let mut bytes = b"attachment; filename=\"".to_vec();
    • bytes.extend_from_slice(filename.as_bytes());
    • bytes.push(b'\"');
  • axum-extra/src/response/file_stream.rs:279
    • format!("attachment; filename=\"{file_name}\"")

For context, this looks very similar in class to CVE-2023-29401 (Gin file attachment filename handling):

I might be misunderstanding intended behavior, so please feel free to correct me.

If this behavior is expected, I’d appreciate guidance on the recommended safe usage pattern (and I can open a docs-focused follow-up if helpful).

If you think this should be adjusted in code, I’d be happy to prepare a small PR with a proposed escaping approach and regression tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions