Files
shopdb-flask/shopdb/utils/uploads.py

47 lines
2.0 KiB
Python

"""Serving user-uploaded files without handing the browser a script.
Uploads reach these routes from people, and one of the accepted image types is
not inert: an SVG is an XML document that may carry <script>, and it is wanted
for floor-plan maps and branding, where vector is the right format. Loaded
through an <img> tag a script inside it never runs, so the tiles and maps in
this app were never the risk. Opening the file's own URL is - and the
application image route is public, so the URL is reachable without a session.
Rather than banning the format that has a real use, every uploaded file is
served with headers that make the document case inert:
Content-Security-Policy: default-src 'none'; sandbox
No script, no fetch, no plugin, and a unique opaque origin. This is what
stops an SVG (or an HTML file that talked its way past an allowlist)
executing in the site's origin and reading a token out of localStorage.
X-Content-Type-Options: nosniff
A file whose extension and bytes disagree is not re-typed by the browser
into something executable.
Both are cheap and apply to every upload route, so a new one added later starts
from the same place instead of repeating the reasoning.
"""
from flask import send_from_directory
# 'sandbox' with no allow-* tokens: opaque origin, scripts blocked, forms and
# popups blocked. default-src 'none' is belt and braces for a UA that ignores
# sandbox in a header.
UPLOAD_CSP = "default-src 'none'; sandbox"
def harden_upload_response(response):
"""Apply the inert-document headers to a response serving an upload."""
response.headers['Content-Security-Policy'] = UPLOAD_CSP
response.headers['X-Content-Type-Options'] = 'nosniff'
return response
def send_upload(directory, filename, **kwargs):
"""send_from_directory, with the upload headers applied.
Any keyword send_from_directory takes is passed through, so a route that
already sends as an attachment keeps doing so.
"""
return harden_upload_response(send_from_directory(directory, filename, **kwargs))