Skip to content

🐛 bug: sanitize attachment/download filenames#4070

Open
gaby wants to merge 5 commits intomainfrom
normalize-filename-in-attachment-and-download
Open

🐛 bug: sanitize attachment/download filenames#4070
gaby wants to merge 5 commits intomainfrom
normalize-filename-in-attachment-and-download

Conversation

@gaby
Copy link
Member

@gaby gaby commented Feb 9, 2026

Motivation

  • Prevent control characters (CR/LF, TAB, NUL) from appearing in the Content-Disposition filename parameter to avoid header injection and unsafe/ambiguous client dialogs.
  • Ensure a safe, non-empty default filename is used when sanitization removes all characters.
  • Expand coverage for attachment/download behavior to verify handling of spaces, nested paths, and control characters.

Description

  • Added sanitizeFilename in res.go which strips \r, \n, \t, and NUL using strings.Map and applied it to DefaultRes.Attachment and DefaultRes.Download with a fallback to "download" when the sanitized name is empty.
  • Adjusted Attachment/Download logic to sanitize the base filename before determining Content-Type and building the Content-Disposition header, while preserving ASCII vs. non-ASCII quoting and filename* behavior.
  • Regenerated small doc comment changes in ctx_interface_gen.go (minor interface comment updates) to match current generation output.
  • Expanded tests in ctx_test.go: added cases to Test_Ctx_Attachment (spaces and nested paths), extended Test_Ctx_Attachment_SanitizesFilenameControls (additional control-placement cases), and added Test_Ctx_Download_SanitizesFilenameControls to assert sanitized headers and absence of raw control bytes.

@gaby gaby requested a review from a team as a code owner February 9, 2026 05:30
Copilot AI review requested due to automatic review settings February 9, 2026 05:30
@gaby gaby requested review from ReneWerner87, efectn and sixcolors and removed request for Copilot February 9, 2026 05:30
@ReneWerner87 ReneWerner87 added this to v3 Feb 9, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gaby, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the security and robustness of file serving by implementing comprehensive filename sanitization for Content-Disposition headers in both attachment and download operations. The changes prevent potential header injection vulnerabilities by stripping control characters from filenames and ensure a safe, non-empty default filename is used when necessary. This is complemented by expanded test coverage to validate the new sanitization logic across various filename scenarios.

Highlights

  • Filename Sanitization: Implemented robust filename sanitization for Content-Disposition headers in Attachment and Download methods to prevent header injection by stripping control characters (CR/LF, TAB, NUL).
  • Default Filename Fallback: Introduced a fallback mechanism to use "download" as a default filename if the sanitization process results in an empty string, ensuring a valid filename is always provided.
  • Expanded Test Coverage: Significantly expanded unit test coverage for Attachment and Download methods, including new test cases for filenames with spaces, nested paths, and various placements of control characters to validate the sanitization logic.
  • New Utility Function: Added a new sanitizeFilename utility function in res.go responsible for removing unwanted control characters from filenames.
  • Documentation Updates: Updated documentation comments in ctx_interface_gen.go for Request, Response, and Value methods to clarify their behavior when the context has been released.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • ctx_interface_gen.go
    • Updated doc comments for Request(), Response(), and Value() methods to clarify behavior when the context is released.
  • ctx_test.go
    • Enhanced Test_Ctx_Attachment with new test cases for filenames containing spaces and nested paths.
    • Added Test_Ctx_Attachment_SanitizesFilenameControls to specifically test the stripping of control characters from attachment filenames and the "download" fallback.
    • Added Test_Ctx_Download_SanitizesFilenameControls to verify control character stripping for download filenames and the "download" fallback.
    • Adjusted Benchmark_Ctx_Attachment to align with the new sanitization logic.
  • res.go
    • Introduced a new sanitizeFilename function to remove carriage return (\r), newline (\n), tab (\t), and null (\x00) characters from strings.
    • Integrated sanitizeFilename into DefaultRes.Attachment and DefaultRes.Download methods, applying it to the base filename and providing a "download" fallback for empty results.
Activity
  • The make audit command was executed and reported vulnerabilities in the Go standard library.
  • Code generation, formatting, and linting tools (make generate, make betteralign, make modernize, make format, make lint) were run successfully.
  • All unit tests, including the newly added ones for filename sanitization, passed successfully.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ReneWerner87 ReneWerner87 added this to the v3 milestone Feb 9, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Walkthrough

Added internal filename sanitization for response Attachment/Download headers to strip control characters and default empty names to "download". Augmented tests for attachment/download behavior and updated inline docs clarifying Request/Response/Value may return nil after context release.

Changes

Cohort / File(s) Summary
Docs: context behavior
ctx_interface_gen.go
Expanded inline comments for Request(), Response(), and Value(key any) to note they may return nil if the context has been released.
Filename sanitization logic
res.go
Added private sanitizeFilename(string) string to remove control characters (CR, LF, tab, NUL) and fallback to "download" for empty results; applied to Attachment and Download header construction and content-type resolution.
Tests: attachment/download sanitization
ctx_test.go
Extended attachment tests for spaces/nested paths; added Test_Ctx_Attachment_SanitizesFilenameControls and Test_Ctx_Download_SanitizesFilenameControls; updated benchmark expectations to match sanitized/normalized filename encoding.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • sixcolors
  • efectn
  • ReneWerner87

Poem

🐰 I scrub the names, I nip the CR and LF,
Tabs and NULs I banish with a hop and deft clef.
When headers form and filenames must be sound,
My little sanitize keeps trouble underground.
Happy downloads — neat, safe, and not a control char found.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers motivation, implementation details, and test changes, but is missing required sections like Changes introduced checklist items, Type of change selection, and Checklist completion. Complete the required sections: select applicable Type of change options, check relevant Checklist items (unit tests, performance), and add explicit Changelog/What's New entry for release notes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely describes the main change: sanitizing filenames in attachment/download functions to fix a security/stability bug with control characters.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch normalize-filename-in-attachment-and-download

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.98%. Comparing base (f93613f) to head (2e439e8).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4070      +/-   ##
==========================================
- Coverage   91.08%   90.98%   -0.10%     
==========================================
  Files         119      119              
  Lines       11221    11254      +33     
==========================================
+ Hits        10221    10240      +19     
- Misses        633      642       +9     
- Partials      367      372       +5     
Flag Coverage Δ
unittests 90.98% <100.00%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces important security hardening by sanitizing filenames used in Content-Disposition headers for attachments and downloads, effectively preventing potential header injection vulnerabilities. The implementation is sound and the added tests provide good coverage for the new logic.

I have a few suggestions to further improve the implementation by making the sanitization more comprehensive and reducing code duplication. These changes would enhance robustness and maintainability.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
res.go (1)

330-340: ⚠️ Potential issue | 🟠 Major

Download should normalize provided filenames with filepath.Base to match Attachment behavior.

When a caller passes a display filename to Download (e.g., c.Download(file, "../../name.txt")), path separators are preserved in the Content-Disposition header. In contrast, Attachment (line 194) calls filepath.Base on the filename parameter before sanitization, which strips any path components. The sanitizeFilename function only removes control characters (\r, \n, \t, \0), not path separators. This inconsistency should be resolved by applying filepath.Base to the explicitly provided filename in Download, as is done in Attachment.

🧹 Nitpick comments (2)
res.go (2)

180-189: Consider stripping all control characters, not just four specific ones.

The current implementation strips \r, \n, \t, and NUL, which covers header injection. However, other C0 control characters (e.g., 0x010x1F) are also invalid/dangerous in filenames and Content-Disposition values. A broader filter would be more defensive:

♻️ Suggested broader control character filter
 func sanitizeFilename(filename string) string {
 	return strings.Map(func(r rune) rune {
-		switch r {
-		case '\r', '\n', '\t', 0:
+		if r < 0x20 || r == 0x7F {
 			return -1
-		default:
-			return r
 		}
+		return r
 	}, filename)
 }

This would also strip BEL, ESC, backspace, and other characters that have no legitimate use in filenames.


192-215: Duplicated Content-Disposition header construction between Attachment and Download.

Lines 200–211 and 341–352 are nearly identical: ASCII check → quote → build disposition string → optional filename* parameter. Consider extracting a shared helper (e.g., buildContentDisposition(fname string)) to reduce duplication and ensure both paths stay consistent as the logic evolves.

Also applies to: 330-354

Copilot AI review requested due to automatic review settings February 9, 2026 06:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@res.go`:
- Around line 196-198: The Attachment code uses filepath.Base(filename[0]) then
sanitizeFilename(fname) but doesn't handle cases where Base returns "." or "/"
(or strings of only slashes), causing invalid Content-Disposition filenames;
update the Attachment handling to check the basename returned from filepath.Base
(and the resulting fname) for ".", "/", "" or strings made only of slashes and
replace them with a safe default like "attachment" or "file" before calling
sanitizeFilename (or sanitize after replacement) so sanitizeFilename is never
given "." or "/" as the filename.
🧹 Nitpick comments (1)
res.go (1)

194-215: Consider extracting the shared Content-Disposition construction logic.

Both Attachment (lines 200–211) and Download (lines 338–349) contain identical logic for building the Content-Disposition header (ASCII check → quote → assemble attachment; filename=… → optional filename*=UTF-8''…). Extracting this into a small helper (e.g., buildContentDisposition(app *App, fname string)) would eliminate the duplication and reduce the risk of the two paths diverging in the future.

♻️ Proposed refactor
+func buildContentDisposition(app *App, fname string) string {
+	var quoted string
+	if app.isASCII(fname) {
+		quoted = app.quoteString(fname)
+	} else {
+		quoted = app.quoteRawString(fname)
+	}
+	disp := `attachment; filename="` + quoted + `"`
+	if !app.isASCII(fname) {
+		disp += `; filename*=UTF-8''` + url.PathEscape(fname)
+	}
+	return disp
+}

Then in Attachment:

 		fname = sanitizeFilename(fname)
 		r.Type(filepath.Ext(fname))
-		app := r.c.app
-		var quoted string
-		if app.isASCII(fname) {
-			quoted = app.quoteString(fname)
-		} else {
-			quoted = app.quoteRawString(fname)
-		}
-		disp := `attachment; filename="` + quoted + `"`
-		if !app.isASCII(fname) {
-			disp += `; filename*=UTF-8''` + url.PathEscape(fname)
-		}
-		r.setCanonical(HeaderContentDisposition, disp)
+		r.setCanonical(HeaderContentDisposition, buildContentDisposition(r.c.app, fname))

And similarly in Download.

Also applies to: 330-351

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Content-Disposition handling for Attachment and Download by stripping control characters from the filename to prevent unsafe header values, and expands tests to validate expected sanitization behavior.

Changes:

  • Added sanitizeFilename and applied it to DefaultRes.Attachment and DefaultRes.Download, with a non-empty fallback name.
  • Updated attachment/download header expectations (notably removing CR/LF/TAB/NUL from encoded output).
  • Expanded ctx_test.go coverage for spaces, nested paths, and multiple control-character placements; regenerated interface doc comments.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
res.go Introduces filename sanitization and applies it to attachment/download Content-Disposition generation.
ctx_test.go Adds/updates tests to assert control-character stripping and some additional filename cases.
ctx_interface_gen.go Regenerates/updates interface comments related to released contexts.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

{
name: "empty fallback",
filename: "\r\n\t\x00",
expected: `attachment; filename="download"`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of default download filename

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants