The invoice number a user types ends up inside an HTTP header
A five minute check for anyone who lets a user name a thing and later offers that thing as a download.
On an invoice, the number is text the user typed. It is a form field, and in the API it is a JSON string with nothing on it but a length cap. That is right for the document: 2026/07-001 is a perfectly good thing to print on an invoice. The trouble is where else the number goes. It becomes part of the file name, and the file name goes into a Content-Disposition header on the download, into the attachment name on the email that reaches the client of the seller, and into a path when a command writes the PDF to disk.
In August a review tried it against real responses. A number containing a double quote closed the filename early, and everything after it was read as further header parameters. A number containing a newline made the header invalid, and the download answered 500 on an invoice that rendered fine everywhere else. A slash or two dots in the same number would have written the PDF outside the directory it names.
The embarrassing part: the free generator already stripped its number, with a comment saying why. The four other places that built a file name did not. Same defect, fixed in one place out of five.
The fix was one small class that every caller uses. It keeps letters, digits, dash and underscore, drops everything else, and falls back to the document type alone if nothing is left. It strips for the file name only. The document still prints exactly what the user typed, because refusing a slash in an invoice number would be the filename rule reaching into the document.
The check for your stack: search for Content-Disposition, attachment names and file paths, and for each one ask where the name comes from. If any part of it is text a user typed, try a double quote, a newline and two dots in that field, and read the raw response rather than the browser download.
Where else does user text end up as syntax in your product?
Replies