From abf8260115c12d441318f31e925d663168d198fb Mon Sep 17 00:00:00 2001 From: pmustonebi Date: Thu, 26 Feb 2026 15:50:06 +0000 Subject: [PATCH] Updated to fix security flaws --- rnacentral/portal/views.py | 34 +++++++++++++++++-------------- rnacentral/rnacentral/settings.py | 31 +++++++++++++++++++++++++++- rnacentral/rnacentral/urls.py | 4 ++-- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/rnacentral/portal/views.py b/rnacentral/portal/views.py index 7d4486e6e..b272b0ee7 100644 --- a/rnacentral/portal/views.py +++ b/rnacentral/portal/views.py @@ -809,16 +809,10 @@ def proxy(request): "This proxy is for www.ebi.ac.uk, wwwdev.ebi.ac.uk, mirbase.org, rfam.org or rna.bgsu.edu only." ) - if domain == "rna.bgsu.edu": - # make sure to use the full url - try: - query_string = request.META["QUERY_STRING"] - url = query_string.split("url=")[1] - except IndexError: - pass - try: - proxied_response = requests.get(url) + # timeout prevents slow-loris / hung connections; allow_redirects=False + # prevents redirect chains from escaping the validated domain. + proxied_response = requests.get(url, timeout=10, allow_redirects=False) if proxied_response.status_code == 200: if ( domain == "rfam.org" @@ -1084,12 +1078,22 @@ def docbot_feedback(request): def handler500(request, *args, **argv): """ - Customized version of handler500 with status_code = 200 in order - to make EBI load balancer to proxy pass to this view, instead of displaying 500. - - https://stackoverflow.com/questions/17662928/django-creating-a-custom-500-404-error-page + Custom 500 handler that renders our error template with the correct HTTP status. + + The EBI load balancer concern (previously worked around by returning 200) is + already handled at the nginx layer: nginx.yaml intercepts 5xx responses from + Gunicorn via `error_page 500 /error/` and internally proxies to the /error/ + Django view (which returns 200), so the load balancer never sees the raw 500. + + The load balancer's health probing should use /health-check/ rather than + individual request responses — that endpoint already returns 200/503 based on + real service state and is documented as the Traffic Manager signal. + + NOTE FOR INFRASTRUCTURE TEAM: if the EBI load balancer is still configured to + probe arbitrary page responses rather than /health-check/, please update it to + target /health-check/ instead. This restores correct HTTP semantics and ensures + application errors are visible to monitoring without affecting load-balancer + routing decisions. """ - # warning: in django2 signature of this function has changed response = render(request, "500.html", {}) - response.status_code = 200 return response \ No newline at end of file diff --git a/rnacentral/rnacentral/settings.py b/rnacentral/rnacentral/settings.py index 7437d65bc..cd0053aca 100644 --- a/rnacentral/rnacentral/settings.py +++ b/rnacentral/rnacentral/settings.py @@ -113,6 +113,8 @@ ) MIDDLEWARE = ( + # SecurityMiddleware must be first so security headers are applied to all responses + "django.middleware.security.SecurityMiddleware", # gzip "django.middleware.gzip.GZipMiddleware", # default @@ -160,7 +162,20 @@ USE_ETAGS = True -CORS_ORIGIN_ALLOW_ALL = True +# Restrict CORS to known consumers rather than allowing all origins. +# django-cors-headers evaluates CORS_ALLOWED_ORIGIN_REGEXES when an exact-match +# against CORS_ALLOWED_ORIGINS fails, so both lists are checked. +# +# Add localhost / 127.0.0.1 origins in local_settings.py when running locally: +# CORS_ALLOWED_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:3000"] +CORS_ALLOWED_ORIGIN_REGEXES = [ + # All RNAcentral subdomains (test, sequence-search, blog, search, …) + r"^https://([a-z0-9-]+\.)*rnacentral\.org$", + # All EBI subdomains (www, wwwdev, wwwint, …) + r"^https://([a-z0-9-]+\.)*ebi\.ac\.uk$", + # GitHub Pages hosting the sequence-search embed widget + r"^https://rnacentral\.github\.io$", +] ROOT_URLCONF = "rnacentral.urls" @@ -373,6 +388,20 @@ # Use a simplified runner to prevent any modifications to the database. TEST_RUNNER = "portal.tests.test_runner.FixedRunner" +# Security settings (defence in depth — some may also be enforced at nginx/load-balancer level) +# SECURE_BROWSER_XSS_FILTER is intentionally omitted: it was removed in Django 5.0 because +# the X-XSS-Protection header it set is considered harmful on modern browsers. +SECURE_SSL_REDIRECT = not DEBUG +# Tell SecurityMiddleware that HTTPS is indicated by the X-Forwarded-Proto header set by the +# reverse proxy, so that SECURE_SSL_REDIRECT does not loop on proxied requests. +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SECURE_HSTS_SECONDS = 31536000 # 1 year +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_CONTENT_TYPE_NOSNIFF = True +SESSION_COOKIE_SECURE = not DEBUG +CSRF_COOKIE_SECURE = not DEBUG +X_FRAME_OPTIONS = "DENY" + try: from .local_settings import * except ImportError: diff --git a/rnacentral/rnacentral/urls.py b/rnacentral/rnacentral/urls.py index 08f309f5e..178cede3c 100644 --- a/rnacentral/rnacentral/urls.py +++ b/rnacentral/rnacentral/urls.py @@ -59,6 +59,6 @@ urlpatterns += additional_settings -# Override 500 page, so that in case of an error, we still display our error page with normal response status -# and EBI load balancer still proxies to our website instead of showing an EBI 'service down' page +# Override 500 handler to render our custom error template. +# Load-balancer health probing should target /health-check/, not individual responses. handler500 = "portal.views.handler500"