🐛 bug: sanitize attachment/download filenames#4070
Conversation
Summary of ChangesHello @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 Highlights
🧠 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
Activity
Using Gemini Code AssistThe 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
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 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
|
WalkthroughAdds internal filename sanitization and fallback for response Attachment/Download headers; expands inline context comments to note Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4070 +/- ##
==========================================
- Coverage 91.08% 91.07% -0.02%
==========================================
Files 119 119
Lines 11221 11288 +67
==========================================
+ Hits 10221 10280 +59
- Misses 633 637 +4
- Partials 367 371 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Downloadshould normalize provided filenames withfilepath.Baseto matchAttachmentbehavior.When a caller passes a display filename to
Download(e.g.,c.Download(file, "../../name.txt")), path separators are preserved in theContent-Dispositionheader. In contrast,Attachment(line 194) callsfilepath.Baseon the filename parameter before sanitization, which strips any path components. ThesanitizeFilenamefunction only removes control characters (\r,\n,\t,\0), not path separators. This inconsistency should be resolved by applyingfilepath.Baseto the explicitly provided filename inDownload, as is done inAttachment.
🧹 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.,0x01–0x1F) 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 betweenAttachmentandDownload.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
There was a problem hiding this comment.
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) andDownload(lines 338–349) contain identical logic for building theContent-Dispositionheader (ASCII check → quote → assembleattachment; filename=…→ optionalfilename*=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
There was a problem hiding this comment.
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
sanitizeFilenameand applied it toDefaultRes.AttachmentandDefaultRes.Download, with a non-empty fallback name. - Updated attachment/download header expectations (notably removing CR/LF/TAB/NUL from encoded output).
- Expanded
ctx_test.gocoverage 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. |
…tions no_controls: current ~12 ns vs old strings.Map ~25–28 ns controls_present: current ~37 ns vs old strings.Map ~55 ns
There was a problem hiding this comment.
Actionable comments posted: 1
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)
200-216:⚠️ Potential issue | 🟠 MajorMissing empty-filename fallback after sanitization.
The PR objectives state a fallback to
"download"when the sanitized filename is empty, but neitherAttachmentnorDownloadimplements this guard. If a caller passes a filename composed entirely of control characters (e.g.,"\r\n"),sanitizeFilenamereturns"", producingContent-Disposition: attachment; filename=""and an empty extension forr.Type().Additionally,
filepath.Basecan return"."for inputs like""or".", which survives sanitization but is not a meaningful filename.Proposed fix for Attachment
if len(filename) > 0 { fname := filepath.Base(filename[0]) fname = sanitizeFilename(fname) + if fname == "" || fname == "." { + fname = "download" + } r.Type(filepath.Ext(fname))
🤖 Fix all issues with AI agents
In `@res.go`:
- Around line 337-341: After calling sanitizeFilename on fname (set from
filename[0] or file), add the same empty-filename fallback used in Attachment:
check if fname == "" or fname == "." and if so assign a sensible default (e.g.,
"download" or "file") so Download does not propagate empty or "." names; update
the block that sets fname (using filename, file, and sanitizeFilename) to
include this conditional fallback.
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: d224310 | Previous: 7b92748 | Ratio |
|---|---|---|---|
Benchmark_Ctx_Attachment - B/op |
168 B/op |
104 B/op |
1.62 |
Benchmark_Ctx_Attachment - allocs/op |
4 allocs/op |
2 allocs/op |
2 |
This comment was automatically generated by workflow using github-action-benchmark.
yes we know, but its the same speed -> so okay in terms of security / speed |
Motivation
Content-Dispositionfilename parameter to avoid header injection and unsafe/ambiguous client dialogs.Description
sanitizeFilenameinres.gowhich strips\r,\n,\t, and NUL usingstrings.Mapand applied it toDefaultRes.AttachmentandDefaultRes.Downloadwith a fallback to"download"when the sanitized name is empty.Attachment/Downloadlogic to sanitize the base filename before determiningContent-Typeand building theContent-Dispositionheader, while preserving ASCII vs. non-ASCII quoting andfilename*behavior.ctx_interface_gen.go(minor interface comment updates) to match current generation output.ctx_test.go: added cases toTest_Ctx_Attachment(spaces and nested paths), extendedTest_Ctx_Attachment_SanitizesFilenameControls(additional control-placement cases), and addedTest_Ctx_Download_SanitizesFilenameControlsto assert sanitized headers and absence of raw control bytes.