SecHead
Scansiona un sitoContattaci
Header Guide15 min read

Secure File Download Headers

When serving user-uploaded files, you must ensure they don't accidentally execute malicious code in the browser. Here are the headers you need.

SL
Seven Labs · 21 June 2026
3,010 words

Quick Answer: How to Secure File Downloads To securely serve user-uploaded files and prevent Stored XSS, you must configure your server to return three critical HTTP headers:

  1. Content-Disposition: attachment; filename="safe-filename.ext" to force the browser to download the file instead of rendering it inline.
  2. X-Content-Type-Options: nosniff to prevent the browser from overriding the declared MIME type.
  3. Content-Type: application/octet-stream (or the exact, safe MIME type) to treat unknown files as binary data.

These headers ensure that even if an attacker uploads a malicious HTML or SVG file, the browser will save it to the disk rather than executing its scripts within your application's origin.


Introduction to Secure File Downloads

In the modern web ecosystem, allowing users to upload and share files is a staple feature. From profile avatars and resume submissions to document sharing platforms and cloud storage services, file handling is ubiquitous. However, hosting user-generated files is widely considered one of the most dangerous features a web application can implement. If an attacker manages to upload a file containing malicious code-such as JavaScript embedded within an HTML document or an SVG image-and your server serves that file directly back to other users, the code will execute in the context of your domain. This scenario is a textbook example of a Stored Cross-Site Scripting (XSS) attack.

To protect your users and your application's integrity, relying solely on input validation and file extension checking during the upload process is insufficient. You must implement robust defense-in-depth mechanisms when serving these files. The most critical layer of this defense involves using a specific combination of HTTP response headers to dictate exactly how the browser should handle the downloaded content.

This comprehensive guide, brought to you by SecHead, will explore the intricacies of secure file download headers, focusing primarily on Content-Disposition: attachment, proper MIME type configuration, and strategies for XSS prevention in file uploads. Whether you are a web developer, security engineer, or system administrator, mastering these concepts is essential for maintaining a hardened web infrastructure.

The Risks of Insecure File Downloads

Before diving into the solutions, it is crucial to understand the threat landscape associated with user-uploaded files. When a user requests a file from your server, the browser relies on HTTP response headers to determine how to process the payload. If these headers are missing, misconfigured, or overly permissive, the browser may attempt to render the file inline.

1. Stored Cross-Site Scripting (XSS)

The most immediate and severe risk is Stored XSS. Consider a scenario where an attacker uploads a file named report.html containing the following payload:

<html>
<body>
<script>
  fetch('/api/user/change-email', {
    method: 'POST',
    body: JSON.stringify({ email: 'attacker@evil.com' })
  });
  alert('Your session has been hijacked!');
</script>
</body>
</html>

If your server serves this file with a Content-Type: text/html header, or if the browser's MIME sniffing algorithm determines it is HTML, the browser will render the page and execute the JavaScript. Because the file is hosted on your domain, the script runs with the full privileges of the victim's session, allowing the attacker to steal cookies, perform unauthorized actions, or deface the application.

🚨 CRITICAL VULNERABILITY ALERT
Serving user-uploaded HTML, SVG, XML, or PDF files inline without proper isolation exposes your application to immediate XSS risks. Scripts within these files run in the context of the serving origin.

2. Content Sniffing Attacks

Browsers have historically tried to be "helpful" by analyzing the first few bytes of a file to guess its content type, a process known as MIME sniffing. If an attacker uploads a polyglot file-a file that is valid in multiple formats, such as a GIF image that also contains valid Java archive (JAR) or HTML code-and the server serves it as an image, a vulnerable browser might still sniff the HTML payload and execute it.

3. Drive-by Downloads and Malware Distribution

If an application acts as a conduit for malicious executables, it can inadvertently distribute malware. While HTTP headers cannot magically neutralize a virus, forcing a clear download prompt rather than a silent or disguised execution helps break the attack chain.


Deep Dive: The Content-Disposition Header

The Content-Disposition header is the cornerstone of secure file downloads. Originally defined in RFC 6266, this header indicates if the content is expected to be displayed inline in the browser (as a web page or as part of a web page) or as an attachment that should be downloaded and saved locally.

The attachment Directive

To prevent a browser from executing an untrusted file, you must force the browser to download it to the disk. You achieve this using the attachment directive.

HTTP/2 200 OK
Content-Disposition: attachment; filename="financial_report.pdf"
Content-Type: application/pdf
X-Content-Type-Options: nosniff

When the browser receives the Content-Disposition: attachment header, it bypasses its internal rendering engine entirely. Even if the file is an HTML document containing malicious JavaScript, the browser will display a "Save As" dialog or automatically download the file to the user's local filesystem. Once saved locally, the file is detached from your application's origin. If the user subsequently opens the downloaded HTML file, it will run in the local file scheme context (file:///), meaning it can no longer access your site's cookies or make authenticated API requests to your server.

The filename and filename* Directives

The filename directive specifies the default name the browser should use when saving the file. It is essential to sanitize this filename to prevent directory traversal or file overwrite attacks on the user's system.

If the filename contains non-ASCII characters, you should use the filename* parameter, which supports character encoding as defined in RFC 5987.

HTTP/2 200 OK
Content-Disposition: attachment; filename="report.pdf"; filename*=UTF-8''report%20%E2%82%AC.pdf

Inline vs. Attachment

If you explicitly want a file to be displayed within the browser window (e.g., viewing a PDF or an image), you would use inline.

Content-Disposition: inline

However, you should never use inline for untrusted, user-uploaded files unless you are serving them from a completely isolated, sandbox domain.


Role of MIME Types and X-Content-Type-Options

The Content-Disposition header is powerful, but it must be paired with correct MIME type handling to be fully effective.

1. Specifying a Safe Content-Type

The Content-Type header tells the browser what kind of data the response contains. When serving user uploads, you should never blindly trust the MIME type provided by the user during the upload process (e.g., via the Content-Type header in a multipart/form-data request), as this is easily spoofed.

If you cannot determine the exact, safe MIME type of the file through server-side analysis (like magic byte checking), or if the file is a proprietary binary format, you should default to application/octet-stream.

Content-Type: application/octet-stream

This MIME type explicitly tells the browser that the file is an arbitrary binary blob and should not be parsed or executed, strongly encouraging the browser to download it regardless of other settings.

2. Preventing MIME Sniffing: X-Content-Type-Options

Even if you specify a safe Content-Type like text/plain or application/octet-stream, older browsers (and sometimes modern ones depending on the specific file extension) might still attempt to MIME-sniff the content.

To explicitly disable this behavior, you must send the X-Content-Type-Options header with the value nosniff.

HTTP/2 200 OK
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="log.txt"

This header enforces that the browser strictly respects the provided Content-Type header. If the server says a file is text/plain, the browser will treat it as text, even if the file content starts with <html>.


Architectural Best Practices: The Sandbox Domain

While the headers discussed above provide significant protection, relying solely on headers can sometimes fail if a browser bug ignores a header or if a developer accidentally misconfigures a route.

The industry standard, E-E-A-T recommended approach for maximum security is to serve all user-uploaded content from a separate, isolated domain.

For example, if your main application is hosted at https://sechead.com, you should serve uploads from https://sechead-user-content.com.

url: https://sechead-user-content.com/files/8f7d6a5c.pdf
---
[ Secure Sandbox Environment ]
Serving user uploaded content from an isolated origin.

Why use a Sandbox Domain?

  1. Origin Isolation: The Same-Origin Policy (SOP) ensures that scripts running on sechead-user-content.com cannot access the cookies, LocalStorage, or DOM of sechead.com. Even if an XSS payload successfully executes, it executes in an empty, valueless context.
  2. Cookie Separation: By using a completely different top-level domain (not just a subdomain like uploads.sechead.com), you ensure that session cookies scoped to .sechead.com are not sent to the upload server, mitigating Cross-Site Request Forgery (CSRF) and session hijacking risks on the media domain itself.

When using a sandbox domain, you can safely use Content-Disposition: inline for safe file types (like JPEGs or PNGs) to provide a better user experience, knowing that an execution failure will not compromise the main application.


Server Configuration Snippets

Implementing these headers requires configuration at your web server or application level. Here are snippets for common environments.

Nginx

To add secure headers to a specific directory serving uploads in Nginx:

location /downloads/ {
    # Force attachment
    add_header Content-Disposition 'attachment';
    
    # Disable MIME sniffing
    add_header X-Content-Type-Options nosniff;
    
    # Fallback content type
    default_type application/octet-stream;
    
    # Optional: Prevent framing
    add_header X-Frame-Options DENY;
}

Apache (.htaccess or httpd.conf)

For Apache web servers, you can use the mod_headers module:

<Directory "/var/www/html/uploads">
    # Force attachment
    Header set Content-Disposition "attachment"
    
    # Disable MIME sniffing
    Header set X-Content-Type-Options "nosniff"
    
    # Force default type for everything in this directory
    ForceType application/octet-stream
</Directory>

Node.js (Express)

In an Express.js application, you can set headers dynamically using middleware or directly in your route handlers:

const express = require('express');
const app = express();
const path = require('path');

app.get('/download/:filename', (req, res) => {
    const file = path.join(__dirname, 'uploads', req.params.filename);
    
    // Set headers manually
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('Content-Type', 'application/octet-stream');
    
    // Express provides a built-in download method that handles Content-Disposition
    res.download(file, req.params.filename, (err) => {
        if (err) {
            // Handle error, but don't leak file paths
            res.status(500).send("Error downloading file.");
        }
    });
});

Advanced XSS Prevention in Uploads

While secure download headers protect the delivery of the file, a comprehensive security strategy also requires rigorous validation during the upload phase.

  1. Verify Magic Bytes: Do not trust the file extension or the Content-Type header provided by the client. Read the first few bytes of the uploaded file (the "magic bytes" or file signature) to verify its actual format. For example, a valid JPEG file will always start with FF D8 FF.
  2. Re-encode Images: If a user uploads an image, do not save the raw bytes. Process the image through a graphics library (like ImageMagick or sharp in Node.js). Strip all metadata (EXIF tags) and re-encode the image to a new file. This destroys polyglot payloads and hidden scripts.
  3. Scan for Malware: Integrate an anti-virus engine (like ClamAV) into your upload pipeline to catch known malicious signatures.
  4. Enforce Size Limits: Prevent Denial of Service (DoS) attacks by strictly limiting both the file size and the dimensions of uploaded images.

People Also Ask (PAA)

How do I force a file to download instead of open in the browser? To force a download, configure your server to return the Content-Disposition: attachment HTTP header along with the file. You can also specify a default filename using Content-Disposition: attachment; filename="example.pdf". Additionally, HTML5 provides the download attribute for anchor tags (<a href="/file.pdf" download>), but relying on the server header is much more secure.

What is the MIME type for forced download? The most common MIME type used to force a download for arbitrary or unknown files is application/octet-stream. This tells the browser the file is binary data and shouldn't be executed.

Is it safe to serve user-uploaded HTML files? No, it is extremely dangerous to serve user-uploaded HTML files from your main application domain. Doing so leads to Stored XSS. If you must host user HTML, serve it from an isolated sandbox domain and force it to download using Content-Disposition: attachment.

What does X-Content-Type-Options: nosniff do? This header prevents the browser from trying to analyze the file content to guess its MIME type. It forces the browser to strictly rely on the Content-Type header provided by the server, preventing attacks where an executable file is disguised as an image.


FAQ: Secure File Download Headers

1. Can I use the HTML download attribute instead of the Content-Disposition header? The HTML <a download> attribute is a client-side hint and provides good UX, but it is not a security control. Attackers can bypass the anchor tag and request the file directly via URL. You must always use the Content-Disposition header on the server side for actual security.

2. What happens if I don't set X-Content-Type-Options: nosniff? Without nosniff, a browser (especially Internet Explorer or older versions of Chrome/Firefox) might ignore your Content-Type header. If an attacker uploads a text file containing JavaScript, the browser might sniff it, realize it contains code, and execute it as HTML, bypassing your defenses.

3. Does Content-Disposition: attachment protect against malware? It prevents the automatic execution of malware within the browser context (like a drive-by download or XSS). However, if the user downloads the malware to their disk and manually runs the executable, they will still be compromised. Server-side antivirus scanning is required to mitigate this.

4. How do I handle PDF files securely? PDF files can contain JavaScript and have historically been vectors for browser exploits. For user-uploaded PDFs, you should force them to download using attachment. If you need to display them inline, use a sandbox domain and consider rendering them to images server-side or using a secure JavaScript PDF viewer like PDF.js in a sandboxed iframe.

5. What is a polyglot file? A polyglot file is a file designed to be valid in multiple formats simultaneously. For example, a file that is both a valid GIF image and a valid Java Archive (JAR). Attackers use these to bypass file extension checks and trick browsers into executing malicious payloads.

6. Should I sanitize filenames in the Content-Disposition header? Yes. You should strip out path traversal characters (like ../) and special characters. It is often safest to generate a random UUID for the filename on the backend and only map back to the original filename in the Content-Disposition header after rigorous sanitization.

7. Why use application/octet-stream? application/octet-stream is the default category for binary files. When a browser receives this content type, it knows it lacks the capability to render the file securely, so it defaults to offering a download dialog.

8. Is checking the file extension during upload enough? No. File extensions are trivial to spoof. An attacker can rename malware.exe to image.jpg. Your server must validate the file contents using magic byte inspection and apply secure headers upon download.

9. Can SVGs contain XSS? Yes, SVG (Scalable Vector Graphics) is an XML-based format that supports embedded JavaScript (<script> tags). If you serve a user-uploaded SVG inline, the script will execute. Always sanitize SVGs server-side, serve them with Content-Disposition: attachment, or host them on a sandbox domain.

10. How does a sandbox domain prevent CSRF? If your sandbox domain is completely separate (e.g., usercontent.com instead of app.com), the browser will not attach the user's app.com session cookies when making requests to the sandbox domain. This means any scripts executing on the sandbox domain cannot perform authenticated actions on behalf of the user.

11. Does Cross-Origin Resource Sharing (CORS) help with file downloads? CORS dictates which external domains can read the contents of your files via JavaScript (like fetch). While CORS is important for API security, Content-Disposition and X-Content-Type-Options are the primary mechanisms for preventing the browser from executing the file when a user navigates to it directly.

12. How do I test if my secure headers are working? You can use tools like curl -I https://yoursite.com/file to view the HTTP response headers. Additionally, browser developer tools (Network tab) will show you the exact headers received. Try uploading an HTML file with an alert(1) payload and navigating to it; if the headers are correct, it will download, not pop the alert.

13. Are there any performance impacts to setting these headers? No. Adding a few HTTP headers adds negligible overhead (just a few bytes to the response payload) and has zero negative impact on server performance.

14. What is the difference between inline and attachment? inline instructs the browser to attempt to display the file within the browser window if it has the capability (e.g., showing a PDF or playing a video). attachment forcefully overrides this behavior, requiring the browser to save the file to disk.

15. Should I use secure headers for files my company creates, or only user uploads? While strictly necessary for user-generated content, applying nosniff and proper Content-Type headers to all assets (including your own CSS, JS, and images) is a security best practice that hardens your application against misconfigurations and certain types of content injection attacks.


Conclusion

Securing user file downloads is a critical component of web application security. Failing to implement the correct HTTP headers can lead to catastrophic Stored XSS vulnerabilities and severe data breaches.

By consistently applying Content-Disposition: attachment, explicitly declaring X-Content-Type-Options: nosniff, and utilizing a sandbox domain architecture for user-generated content, you establish a formidable defense-in-depth posture. Remember that security does not stop at the upload form; how your server delivers files is just as important as how it receives them.

Implement these configurations in your web servers and application frameworks today, and ensure that your platforms remain resilient against modern web exploits.



Continue your journey into web security with these related, deep-dive articles from the SecHead team:

SEO Metadata

  • Meta Title: Secure File Download Headers: Preventing XSS in User Uploads
  • Meta Description: Learn how to use Content-Disposition attachment, MIME types, and secure HTTP headers to prevent Stored XSS and secure user file downloads.
  • URL Slug: secure-file-download-headers
  • Keywords: Content-Disposition attachment, Secure File Downloads, MIME Types, XSS Prevention in Uploads, X-Content-Type-Options nosniff, Sandbox Domain

Related articles

Free tool

Check your own security headers

Instant grade, plain-language explanations, and a full remediation plan - no signup needed.

Scan your site now →