Skip to content

Commit

Permalink
Simplify code of the file viewer API and reorganize CSP
Browse files Browse the repository at this point in the history
  • Loading branch information
evilaliv3 committed Jan 23, 2025
1 parent 0eeadbe commit 849ead4
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 87 deletions.
17 changes: 0 additions & 17 deletions backend/globaleaks/handlers/staticfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,4 @@ def get(self, filename):
abspath = os.path.abspath(os.path.join(self.root, filename))
directory_traversal_check(self.root, abspath)

if filename == 'index.html':
self.request.setHeader(b'Content-Security-Policy',
b"base-uri 'none';"
b"connect-src 'self';"
b"default-src 'none';"
b"font-src 'self';"
b"form-action 'none';"
b"frame-ancestors 'none';"
b"frame-src 'self';"
b"img-src 'self';"
b"media-src 'self';"
b"script-src 'self' 'sha256-l4srTx31TC+tE2K4jVVCnC9XfHivkiSs/v+DPWccDDM=' 'report-sample';"
b"style-src 'self' 'report-sample';"
b"trusted-types angular angular#bundler dompurify default;"
b"require-trusted-types-for 'script';"
b"report-uri /api/report;")

return self.write_file(filename, abspath)
34 changes: 0 additions & 34 deletions backend/globaleaks/handlers/viewer.py

This file was deleted.

51 changes: 44 additions & 7 deletions backend/globaleaks/rest/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
staticfile, \
support, \
user, \
viewer, \
wizard, \
whistleblower

Expand Down Expand Up @@ -162,9 +161,6 @@
# Path alias
(r'^(/admin|/login|/submission)$', redirect.SpecialRedirectHandler),

# File viewer app
(r'/(viewer/[a-zA-Z0-9_\-\/\.\@]*)', viewer.ViewerHandler),

# This handler attempts to route all non routed get requests
(r'/([a-zA-Z0-9_\-\/\.\@]*)', staticfile.StaticFileHandler)
]
Expand Down Expand Up @@ -492,6 +488,11 @@ def set_headers(self, request):
if request.tid in State.tenants and State.tenants[request.tid].cache.onionservice:
request.setHeader(b'Onion-Location', b'http://' + State.tenants[request.tid].cache.onionservice.encode() + request.path)

request.setHeader(b"Cross-Origin-Embedder-Policy", "require-corp")
request.setHeader(b"Cross-Origin-Opener-Policy", "same-origin")
request.setHeader(b"Cross-Origin-Resource-Policy", "same-origin")

# Default CSP Policy
request.setHeader(b'Content-Security-Policy',
b"base-uri 'none';"
b"default-src 'none' 'report-sample';"
Expand All @@ -502,9 +503,45 @@ def set_headers(self, request):
b"require-trusted-types-for 'script';"
b"report-uri /api/report;")

request.setHeader(b"Cross-Origin-Embedder-Policy", "require-corp")
request.setHeader(b"Cross-Origin-Opener-Policy", "same-origin")
request.setHeader(b"Cross-Origin-Resource-Policy", "same-origin")
# CSP Policy on the entry point
if request.path == b'/' or request.path == b'/index.html':
request.setHeader(b'Content-Security-Policy',
b"base-uri 'none';"
b"connect-src 'self';"
b"default-src 'none';"
b"font-src 'self';"
b"form-action 'none';"
b"frame-ancestors 'none';"
b"frame-src 'self';"
b"img-src 'self';"
b"media-src 'self';"
b"script-src 'self' 'report-sample';"
b"style-src 'self' 'report-sample';"
b"trusted-types angular angular#bundler dompurify default;"
b"require-trusted-types-for 'script';"
b"report-uri /api/report;")

# CSP Policy for the file viewer
elif request.path.startswith(b'/viewer'):
if request.path == b'/viewer/index.html':
request.setHeader(b'Content-Security-Policy',
b"base-uri 'none';"
b"default-src 'none';"
b"connect-src blob:;"
b"form-action 'none';"
b"frame-ancestors 'self';"
b"img-src blob:;"
b"media-src blob:;"
b"script-src 'self' 'report-sample';"
b"style-src 'self' 'report-sample';"
b"sandbox allow-scripts;"
b"trusted-types;"
b"require-trusted-types-for 'script';"
b"report-uri /api/report;")

request.setHeader(b"Cross-Origin-Resource-Policy", "cross-origin")
else:
request.setHeader(b'Access-Control-Allow-Origin', "null")

# Disable features that could be used to deanonymize the user
microphone = False
Expand Down
129 changes: 100 additions & 29 deletions backend/globaleaks/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import copy
from twisted.internet.address import IPv4Address
from twisted.internet.defer import inlineCallbacks

Expand Down Expand Up @@ -67,40 +68,110 @@ def test_status_codes_and_headers(self):
(b'XXX', 501)
]

server_headers = [
('Cache-control', 'no-store'),
('Content-Security-Policy', 'base-uri \'none\';' \
'default-src \'none\' \'report-sample\';' \
'form-action \'none\';' \
'frame-ancestors \'none\';' \
'sandbox;' \
'trusted-types;' \
'require-trusted-types-for \'script\';'
'report-uri /api/report;'),
('Cross-Origin-Embedder-Policy', 'require-corp'),
('Cross-Origin-Opener-Policy', 'same-origin'),
('Cross-Origin-Resource-Policy', 'same-origin'),
('Permissions-Policy', "camera=(),"
"document-domain=(),"
"fullscreen=(),"
"geolocation=(),"
"microphone=(),"
"serial=(),"
"usb=(),"
"web-share=()"),
('Referrer-Policy', 'no-referrer'),
('Server', 'GlobaLeaks'),
('X-Content-Type-Options', 'nosniff'),
('X-Check-Tor', 'False'),
('X-Frame-Options', 'deny')
]
default_server_headers = {
'Cache-control': 'no-store',
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Resource-Policy': 'same-origin',
'Permissions-Policy': 'camera=(),'
'document-domain=(),'
'fullscreen=(),'
'geolocation=(),'
'microphone=(),'
'serial=(),'
'usb=(),'
'web-share=()',
'Content-Security-Policy': 'base-uri \'none\';'
'default-src \'none\' \'report-sample\';'
'form-action \'none\';'
'frame-ancestors \'none\';'
'sandbox;'
'trusted-types;'
'require-trusted-types-for \'script\';'
'report-uri /api/report;',
'Referrer-Policy': 'no-referrer',
'Server': 'GlobaLeaks',
'X-Content-Type-Options': 'nosniff',
'X-Check-Tor': 'False',
'X-Frame-Options': 'deny'
}

server_headers = copy.copy(default_server_headers)
server_headers['Content-Security-Policy'] = 'base-uri \'none\';' \
'connect-src \'self\';' \
'default-src \'none\';' \
'font-src \'self\';' \
'form-action \'none\';' \
'frame-ancestors \'none\';' \
'frame-src \'self\';' \
'img-src \'self\';' \
'media-src \'self\';' \
'script-src \'self\' \'report-sample\';' \
'style-src \'self\' \'report-sample\';' \
'trusted-types angular angular#bundler dompurify default;' \
'require-trusted-types-for \'script\';' \
'report-uri /api/report;'

for method, status_code in test_cases:
request = forge_request(uri=b"https://www.globaleaks.org/", method=method)
self.api.render(request)
self.assertEqual(request.responseCode, status_code)
for headerName, expectedHeaderValue in server_headers:
returnedHeaderValue = request.responseHeaders.getRawHeaders(headerName)[0]
for headerName, expectedHeaderValue in server_headers.items():
returnedHeaderValue = request.responseHeaders.getRawHeaders(headerName)[-1]
self.assertEqual(returnedHeaderValue, expectedHeaderValue)

server_headers = copy.copy(default_server_headers)
server_headers['Content-Security-Policy'] = 'base-uri \'none\';' \
'default-src \'none\' \'report-sample\';' \
'form-action \'none\';' \
'frame-ancestors \'none\';' \
'sandbox;' \
'trusted-types;' \
'require-trusted-types-for \'script\';' \
'report-uri /api/report;'

for method, status_code in test_cases:
request = forge_request(uri=b"https://www.globaleaks.org/api/public", method=method)
self.api.render(request)
self.assertEqual(request.responseCode, status_code)
for headerName, expectedHeaderValue in server_headers.items():
returnedHeaderValue = request.responseHeaders.getRawHeaders(headerName)[-1]
self.assertEqual(returnedHeaderValue, expectedHeaderValue)

server_headers = copy.copy(default_server_headers)
server_headers['Content-Security-Policy'] = 'base-uri \'none\';' \
'default-src \'none\';' \
'connect-src blob:;' \
'form-action \'none\';' \
'frame-ancestors \'self\';' \
'img-src blob:;' \
'media-src blob:;' \
'script-src \'self\' \'report-sample\';' \
'style-src \'self\' \'report-sample\';' \
'sandbox allow-scripts;' \
'trusted-types;' \
'require-trusted-types-for \'script\';' \
'report-uri /api/report;'

server_headers['Cross-Origin-Resource-Policy'] = 'cross-origin'

for method, status_code in test_cases:
request = forge_request(uri=b"https://www.globaleaks.org/viewer/index.html", method=method)
self.api.render(request)
self.assertEqual(request.responseCode, status_code)
for headerName, expectedHeaderValue in server_headers.items():
returnedHeaderValue = request.responseHeaders.getRawHeaders(headerName)[-1]
self.assertEqual(returnedHeaderValue, expectedHeaderValue)

server_headers = copy.copy(default_server_headers)
server_headers['Access-Control-Allow-Origin'] = 'null'

for method, status_code in test_cases:
request = forge_request(uri=b"https://www.globaleaks.org/viewer/script.js", method=method)
self.api.render(request)
self.assertEqual(request.responseCode, status_code)
for headerName, expectedHeaderValue in server_headers.items():
returnedHeaderValue = request.responseHeaders.getRawHeaders(headerName)[-1]
self.assertEqual(returnedHeaderValue, expectedHeaderValue)

def test_request_state_and_redirects(self):
Expand Down

0 comments on commit 849ead4

Please sign in to comment.