Add support for 'raw' parameter for source files

This parameter makes the server return a response with raw file
contents and headers that cause the browser to show a 'save as' dialog.

Regarding Content-Security-Policy:

https://www.w3.org/TR/CSP2/#sandbox-usage

> For example, a message board or email system might provide downloads of
> arbitrary attachments provided by other users. Attacks that rely on tricking
> a client into rendering one of these attachments could be mitigated by
> requesting that resources only be rendered in a very restrictive sandbox.
> Sending the sandbox directive with an empty value establishes such an
> environment:
>
> Content-Security-Policy: sandbox

https://www.w3.org/TR/CSP2/#directive-default-src

> Given this behavior, one good way of building a policy for a site would be to
> begin with a default-src of 'none', and to build up a policy from there that
> contains only those resource types which are actually in use for the page
> you’d like to protect. If you don’t use webfonts, for instance, there’s no
> reason to specify a source list for font-src; specifying only those resource
> types a page uses ensures that the possible attack surface for that page
> remains as small as possible.

https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html#defense-in-depth

> A strong CSP provides an effective second layer of protection against various
> types of vulnerabilities, especially XSS. Although CSP doesn't prevent web
> applications from containing vulnerabilities, it can make those
> vulnerabilities significantly more difficult for an attacker to exploit.

The idea is to prevent the browser from loading any external resources, if it
turned out it's possible to trick it into interpreting a file as HTML.
This commit is contained in:
Franciszek Stachura 2024-08-26 20:27:48 +02:00
parent 4058a4b4f0
commit 8c5e12fffd
4 changed files with 32 additions and 3 deletions

View file

@ -95,8 +95,12 @@ class SourceResource:
resp.location = stringify_source_path(project, version, path)
return
resp.content_type = falcon.MEDIA_HTML
resp.status, resp.text = generate_source_page(req.context, query, project, version, path)
raw_param = req.get_param('raw')
if raw_param is not None and raw_param.strip() != '0':
generate_raw_source(resp, query, version, path)
else:
resp.content_type = falcon.MEDIA_HTML
resp.status, resp.text = generate_source_page(req.context, query, project, version, path)
# Handles source URLs without a path, ex. '/u-boot/v2023.10/source'.
# Note lack of trailing slash
@ -255,6 +259,20 @@ def get_layout_template_context(q, ctx, get_url_with_new_version, project, versi
'current_family': 'A',
}
# Generate raw source response
def generate_raw_source(resp, query, version, path):
type = query.query('type', version, path)
if type != 'blob':
raise falcon.HTTPNotFound('Error', 'File not found')
else:
code = query.get_file_raw(version, path)
resp.content_type = 'application/octet-stream'
resp.text = code
resp.downloadable_as = path.split('/')[-1]
# Cache for 24 hours
resp.cache_control = ('max-age=86400',)
# Sandbox result just in case
resp.headers['Content-Security-Policy'] = "sandbox; default-src 'none'"
# Guesses file format based on filename, returns code formatted as HTML
def format_code(filename, code):
@ -321,7 +339,6 @@ def generate_source(q, project, version, path):
return html_code_block
# Represents a file entry in git tree
# type: either tree (directory), blob (file) or symlink
# name: filename of the file

View file

@ -404,6 +404,9 @@ h2 {
box-shadow: 0 0 0 1px #666;
}
#file-download-link {
margin-left: 1rem;
}
/* sidebar */

View file

@ -42,6 +42,8 @@
<a href="{{ source_base_url }}">{{ current_project }}</a>
<em class="icon-tag">{{ current_tag }}</em>
</span>
{% block footer %}
{% endblock %}
<a title="Go to top of the page" class="go-top icon-up screenreader" href="#">
Top
</a>

View file

@ -13,3 +13,10 @@
{{ code }}
</div>
{% endblock %}
{% block footer %}
<span id="file-download-link">
<a href="?raw">Download file</a>
</span>
{% endblock %}