From c82483e39bdcde4a91bff23b802e16b3ecb30aaa Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Wed, 24 Jun 2026 11:01:09 -0300 Subject: [PATCH 01/13] test: mock NVD API calls, add nvd_integration marker - conftest.py patches requests.get + time.sleep in cve_fetcher module - Skips mock for tests marked @pytest.mark.nvd_integration - Moves test_cve_fetcher.py module-level code into fixtures - pytest.ini excludes nvd_integration by default 66 tests now run in ~1s vs timing out at >120s --- backend/analyzer/test/conftest.py | 65 +++++++++++++++++++++++ backend/analyzer/test/test_cve_fetcher.py | 31 +++++++---- backend/pytest.ini | 6 ++- 3 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 backend/analyzer/test/conftest.py diff --git a/backend/analyzer/test/conftest.py b/backend/analyzer/test/conftest.py new file mode 100644 index 0000000..76f0f24 --- /dev/null +++ b/backend/analyzer/test/conftest.py @@ -0,0 +1,65 @@ +from unittest.mock import patch + +import pytest + +MOCK_NVD_RESPONSE = { + "vulnerabilities": [ + { + "cve": { + "id": "CVE-2021-44228", + "published": "2025-01-01T00:00:00Z", + "lastModified": "2025-01-01T00:00:00Z", + "descriptions": [{"value": "A test vulnerability description."}], + "metrics": { + "cvssMetricV31": [ + { + "cvssData": { + "baseScore": 7.5, + "baseSeverity": "HIGH", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "scope": "UNCHANGED", + } + } + ] + }, + "weaknesses": [{"description": [{"value": "CWE-94"}]}], + "references": [{"url": "https://example.com/advisory", "tags": ["Vendor Advisory"]}], + } + } + ] +} + +MOCK_EPSS_RESPONSE = {"data": [{"epss": 0.5}]} + + +def _mock_requests_get(url, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + url_str = str(url) + if "nvd.nist.gov" in url_str: + return MockResponse(MOCK_NVD_RESPONSE, 200) + if "api.first.org" in url_str: + return MockResponse(MOCK_EPSS_RESPONSE, 200) + raise ConnectionError(f"Unexpected request: {url_str}") + + +@pytest.fixture(autouse=True) +def no_nvd_network(request): + if request.node.get_closest_marker("nvd_integration"): + yield + return + with patch("analyzer.services.cve_fetcher.requests.get", side_effect=_mock_requests_get), \ + patch("analyzer.services.cve_fetcher.time.sleep"): + yield diff --git a/backend/analyzer/test/test_cve_fetcher.py b/backend/analyzer/test/test_cve_fetcher.py index b1df86b..d4072f5 100644 --- a/backend/analyzer/test/test_cve_fetcher.py +++ b/backend/analyzer/test/test_cve_fetcher.py @@ -1,29 +1,42 @@ from datetime import datetime +import pytest + from analyzer.services.cve_fetcher import CVEFetcher from utilities.constants import BaseSeverity, AttackVector, AttackComplexity, UserInteraction, IntegrityImpact, \ AvailabilityImpact, ConfidentialityImpact, Scope, PrivilegesRequired cve_id = "CVE-2021-44228" -cve_fetcher = CVEFetcher(cve_id=cve_id) -cve_data = cve_fetcher.generate() -def test_description(): +@pytest.fixture +def cve_data(): + fetcher = CVEFetcher(cve_id=cve_id) + return fetcher.generate() + + +@pytest.mark.nvd_integration +def test_real_nvd_api_contract(): + fetcher = CVEFetcher(cve_id=cve_id) + data = fetcher.generate() + assert fetcher.successful + assert len(data["description"]) > 0 + assert 0 < data["cve_attributes"]["baseScore"] <= 10 + + +def test_description(cve_data): assert len(cve_data["description"]) > 0 -def test_dates(): +def test_dates(cve_data): published = cve_data["published"] assert isinstance(published, datetime) - updated = cve_data["updated"] assert isinstance(updated, datetime) -def test_cve_attributes_cvss_v3(): +def test_cve_attributes_cvss_v3(cve_data): attributes = cve_data["cve_attributes"] - assert 0 < attributes["baseScore"] <= 10 assert attributes["baseSeverity"] in BaseSeverity.names assert attributes["attackVector"] in AttackVector.names @@ -36,9 +49,9 @@ def test_cve_attributes_cvss_v3(): assert attributes["scope"] in Scope.names -def test_epss_score(): +def test_epss_score(cve_data): assert 0 <= float(cve_data["epss"]) <= 1.0 -def test_vendor_reference(): +def test_vendor_reference(cve_data): assert len(cve_data["vendor_reference"]) >= 0 diff --git a/backend/pytest.ini b/backend/pytest.ini index ac3d988..a8cc62a 100644 --- a/backend/pytest.ini +++ b/backend/pytest.ini @@ -1,3 +1,5 @@ [pytest] -addopts = --nomigrations --reuse-db -DJANGO_SETTINGS_MODULE = securecheckplus.settings \ No newline at end of file +addopts = --nomigrations --reuse-db -m "not nvd_integration" +DJANGO_SETTINGS_MODULE = securecheckplus.settings +markers = + nvd_integration: tests that call the real NVD API (requires internet + API key) \ No newline at end of file From c5c6680113a7daed125245e88f89cb19e43bbbb3 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:45:28 -0300 Subject: [PATCH 02/13] fix: guard Docker Login and add hybrid fallback for DB/SALT --- .github/workflows/cicd-actions.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd-actions.yml b/.github/workflows/cicd-actions.yml index dffbe4d..36c0355 100644 --- a/.github/workflows/cicd-actions.yml +++ b/.github/workflows/cicd-actions.yml @@ -26,10 +26,13 @@ jobs: uses: actions/checkout@v4 - name: Create .env file + env: + POSTGRES_PASSWORD: ${{ secrets.TEST_DB_PASSWORD }} + SALT: ${{ secrets.TEST_SALT }} run: | echo "NVD_API_KEY=${{ secrets.TEST_NVD_API_KEY }}" >> .env echo 'DJANGO_SECRET_KEY="${{ secrets.TEST_DJANGO_SECRET_KEY }}"' >> .env - echo 'SALT="${{ secrets.TEST_SALT }}"' >> .env + echo "SALT=${SALT:-local-dev-salt}" >> .env echo "ADMIN_USERNAME=admin@acme.de" >> .env echo "ADMIN_PASSWORD=secure!" >> .env echo "USER_USERNAME=user@acme.de" >> .env @@ -41,7 +44,7 @@ jobs: echo "POSTGRES_USER=securecheckplus" >> .env echo "POSTGRES_DB=securecheckplus" >> .env echo "POSTGRES_PORT=5432" >> .env - echo 'POSTGRES_PASSWORD="${{ secrets.TEST_DB_PASSWORD }}"' >> .env + echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-scp_test_pass}" >> .env echo "EMAIL_HOST=localhost" >> .env echo "EMAIL_PORT=25" >> .env echo 'LDAP_ORGANISATION="ACME"' >> .env @@ -77,6 +80,7 @@ jobs: path: backend - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Build Docker Compose @@ -182,6 +186,7 @@ jobs: path: backend/assets - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Extract metadata (tags, labels) for Docker @@ -221,6 +226,7 @@ jobs: path: backend - name: Docker Login + if: env.DOCKER_USER != '' && env.DOCKER_KEY != '' run: echo "$DOCKER_KEY" | docker login -u "$DOCKER_USER" --password-stdin - name: Extract metadata (tags, labels) for Docker From 59b4dba4102a1cc35f9e419e19b42ef5dcc8a9cf Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:17 -0300 Subject: [PATCH 03/13] security: fix timing attack on API key verification (C1) Use hmac.compare_digest for constant-time string comparison to prevent timing side-channel attacks that could allow an attacker to recover API keys character by character. Fixes C1 from security audit. --- backend/analyzer/manager/project_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/analyzer/manager/project_manager.py b/backend/analyzer/manager/project_manager.py index 8f06d26..1f3e0a3 100644 --- a/backend/analyzer/manager/project_manager.py +++ b/backend/analyzer/manager/project_manager.py @@ -1,3 +1,4 @@ +import hmac import logging from secrets import token_urlsafe @@ -44,7 +45,7 @@ def verify_key(self, key: str) -> bool: """ try: - if self.project.api_key_hash == hash_key(key): + if hmac.compare_digest(self.project.api_key_hash, hash_key(key)): logging.info( f"Authentication with API-Key for {self.project.project_id} successful.") return True From 79e9fd7a2f4cfdf128db863822c57e3373bdb5a9 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:24 -0300 Subject: [PATCH 04/13] security: prevent LDAP injection via username escaping (C2) Use ldap3.utils.conv.escape_filter_chars to escape special characters in LDAP search filters before interpolation. Prevents authentication bypass and data exfiltration via crafted username values. Fixes C2 from security audit. --- backend/webserver/manager/ldap_adapter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/webserver/manager/ldap_adapter.py b/backend/webserver/manager/ldap_adapter.py index 85f33ec..b8b75ae 100644 --- a/backend/webserver/manager/ldap_adapter.py +++ b/backend/webserver/manager/ldap_adapter.py @@ -1,6 +1,7 @@ import logging from ldap3 import Server, ALL, Connection +from ldap3.utils.conv import escape_filter_chars logger = logging.getLogger(__name__) @@ -22,7 +23,8 @@ def __init__( self._user_search_filter = user_search_filter def get_ldap_user(self, username:str): - search_filter = self._user_search_filter.replace("VALUE", username) + safe_username = escape_filter_chars(username) + search_filter = self._user_search_filter.replace("VALUE", safe_username) return search_filter def admin_login(self) -> Connection: From 03b1ec366d6eec3b5dd3d1b30581ec26ff92922f Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:31 -0300 Subject: [PATCH 05/13] security: guard dev auth with IS_DEV check (C3) Only allow hardcoded dev credentials when IS_DEV=True to prevent accidental exposure in production. Use hmac.compare_digest for constant-time password comparison to prevent timing attacks on credential validation. Fixes C3 from security audit. --- backend/webserver/manager/authentication_manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/webserver/manager/authentication_manager.py b/backend/webserver/manager/authentication_manager.py index c5db7d7..48f81c6 100644 --- a/backend/webserver/manager/authentication_manager.py +++ b/backend/webserver/manager/authentication_manager.py @@ -1,5 +1,6 @@ import logging import os +import hmac import ldap3.core.exceptions from django.contrib import auth @@ -7,7 +8,7 @@ from django.contrib.auth.models import Group from securecheckplus.settings import ADMIN_USERNAME, ADMIN_PASSWORD, USER_PASSWORD, USER_USERNAME, LDAP_HOST, LDAP_USER_BASE_DN, LDAP_ADMIN_GROUP_DN, LDAP_BASE_GROUP_DN, \ - LDAP_ADMIN_DN, LDAP_ADMIN_PASSWORD, LDAP_USER_SEARCH_FILTER + LDAP_ADMIN_DN, LDAP_ADMIN_PASSWORD, LDAP_USER_SEARCH_FILTER, IS_DEV from utilities.exceptions import Unauthorized from webserver.manager.ldap_adapter import LdapAdapter from webserver.models import User @@ -86,12 +87,12 @@ def authenticate(self, request, username=None, password=None, **kwargs): logger.warning(f"User '{username}' is not a member of required groups!") return None - elif ADMIN_USERNAME and ADMIN_PASSWORD and ADMIN_USERNAME == username and ADMIN_PASSWORD == password: + elif IS_DEV and ADMIN_USERNAME and ADMIN_PASSWORD and ADMIN_USERNAME == username and hmac.compare_digest(ADMIN_PASSWORD, password): user = User.objects.get_or_create(username=username)[0] user.groups.add(Group.objects.get_or_create(name="admin")[0]) return user - elif USER_USERNAME and USER_PASSWORD and USER_USERNAME == username and USER_PASSWORD == password: + elif IS_DEV and USER_USERNAME and USER_PASSWORD and USER_USERNAME == username and hmac.compare_digest(USER_PASSWORD, password): user = User.objects.get_or_create(username=username)[0] return user From 231479120f5472edbcf629aa4adb5c2ec5340d4f Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:37 -0300 Subject: [PATCH 06/13] security: add permission check to DeleteProjectAPI (H1) Add IsAuthenticated and analyzer.delete_project permission requirements to DeleteProjectAPI to prevent IDOR. Any authenticated user could previously delete any project. Fixes H1 from security audit. --- backend/webserver/views/project_views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/webserver/views/project_views.py b/backend/webserver/views/project_views.py index 351199b..3e737f9 100644 --- a/backend/webserver/views/project_views.py +++ b/backend/webserver/views/project_views.py @@ -20,6 +20,8 @@ class DeleteProjectAPI(APIView): + permission_classes = [IsAuthenticated, permission_required("analyzer.delete_project")] + def post(self, request) -> Response: try: projectIds = request.data["projectIds"] From 030d4ab8c25c0b96aa9fea3d328959887e9cac56 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:44 -0300 Subject: [PATCH 07/13] security: validate CVE ID format and add request timeouts (H2) Add regex validation for CVE ID format to prevent SSRF via crafted CVE IDs. Add 30-second timeout to NVD and EPSS API requests to prevent slow-loris DoS attacks. Fixes H2 from security audit. --- backend/analyzer/services/cve_fetcher.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/analyzer/services/cve_fetcher.py b/backend/analyzer/services/cve_fetcher.py index 8fc1c10..61ca856 100644 --- a/backend/analyzer/services/cve_fetcher.py +++ b/backend/analyzer/services/cve_fetcher.py @@ -1,4 +1,5 @@ import logging +import re import time from urllib import parse @@ -11,6 +12,8 @@ logger = logging.getLogger(__name__) +CVE_ID_PATTERN = re.compile(r'^CVE-\d{4}-\d{4,}$') + class CVEFetcher: """ @@ -61,7 +64,12 @@ def __init__(self, cve_id: str): Args: cve_id (str): The CVE identifier to fetch data for. + + Raises: + ValueError: If the cve_id does not match the expected CVE format. """ + if not CVE_ID_PATTERN.match(cve_id): + raise ValueError(f"Invalid CVE ID format: {cve_id}") self.cve_id = cve_id self.data = {} self.successful = False @@ -80,7 +88,7 @@ def fetch_from_nist_gov(self): headers = {"apiKey": NVD_API_KEY} url = parse.urlunparse(NVD_ADDRESS) + self.cve_id logger.info(f"Fetching CVE data from NIST for CVE ID: {self.cve_id} using URL: {url}") - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=30) if response.status_code != 200: logger.warning( @@ -138,7 +146,7 @@ def fetch_epss(self): try: url = parse.urlunparse(EPSS_ADDRESS) + self.cve_id logger.info(f"Fetching EPSS score for CVE ID: {self.cve_id} using URL: {url}") - epss_response = requests.get(url) + epss_response = requests.get(url, timeout=30) if epss_response.status_code != 200: raise RequestException( From 835b2a2535cb4d28ec3abf641e3793e1f9d604ee Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:27:03 -0300 Subject: [PATCH 08/13] security: whitelist templates in HtmlView (H5) Add ALLOWED_TEMPLATES frozenset to HtmlView to prevent template injection. Non-whitelisted template names are logged and the login page is rendered instead. This mitigates information disclosure via path traversal. Fixes H5 from security audit. --- backend/webserver/views/misc_views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/webserver/views/misc_views.py b/backend/webserver/views/misc_views.py index bb71ea4..18185f2 100644 --- a/backend/webserver/views/misc_views.py +++ b/backend/webserver/views/misc_views.py @@ -23,9 +23,20 @@ class HtmlView(View): + ALLOWED_TEMPLATES = frozenset([ + "index", "app", "dashboard", "projects", "reports", + "project", "report", "settings", "favorites", "dependencies", + ]) def get(self, request, template_name): + if template_name not in self.ALLOWED_TEMPLATES: + logger.warning( + f"Blocked template access attempt: {template_name} " + f"from {request.META.get('HTTP_X_FORWARDED_FOR')}" + ) + return render(request, "login.html", {"IS_DEV": IS_DEV, "BASE_URL": BASE_URL or "", "PREFIX": "/"}) + PREFIX = "/" if not IS_DEV and BASE_URL: From 25b1a4c1058da7b5634872030341a195aa699df8 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:27:18 -0300 Subject: [PATCH 09/13] security: improve Docker security configuration (L1/L2/L3) L1: Use specific UID (1000) for baseuser to avoid permission issues with volume mounts. L2: Add HEALTHCHECK instruction using Python urllib to verify the application is responding. L3: Make gunicorn workers and threads configurable via GUNICORN_WORKERS and GUNICORN_THREADS env vars. Fixes L1, L2, L3 from security audit. --- backend/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index d08bfcc..75df921 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -26,10 +26,13 @@ ARG BUILD=1 COPY . /backend -RUN adduser -D baseuser && chown -R baseuser . +RUN adduser -D -u 1000 baseuser && chown -R baseuser . USER baseuser EXPOSE 8000 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/check_health', timeout=2).read()" || exit 1 + # Overwrites previous CMD from stage "dev" -#CMD gunicorn securecheckplus.wsgi:application --bind 0.0.0.0:8000 --workers=2 --threads=2 --log-level INFO +CMD ["sh", "-c", "exec gunicorn securecheckplus.wsgi:application --bind 0.0.0.0:8000 --workers=${GUNICORN_WORKERS:-2} --threads=${GUNICORN_THREADS:-2} --log-level ${LOG_LEVEL:-INFO}"] From b41e3e96bc8ed755064ca6114bbc69707f6951f2 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Fri, 26 Jun 2026 11:26:56 -0300 Subject: [PATCH 10/13] security: rate limiting, dev creds, CORS, HTTPS settings (H3/H4/M2/M3) H3: Add AnonRateThrottle and UserRateThrottle. Tighten login rate to 5/min to prevent brute-force attacks. H4: Use secrets.token_urlsafe() to generate random dev credentials when not explicitly set. Log warning. M2: Replace CORS_ALLOW_ALL_ORIGINS with explicit origins whitelist (already correct, but add dev origins). M3: Add HTTPS enforcement settings (HSTS, SSL redirect, X-Frame-Options, content type nosniff) for production. Fixes H3, H4, M2, M3 from security audit. --- backend/securecheckplus/settings.py | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/securecheckplus/settings.py b/backend/securecheckplus/settings.py index 13df1d2..65a3fb5 100644 --- a/backend/securecheckplus/settings.py +++ b/backend/securecheckplus/settings.py @@ -13,6 +13,7 @@ import json import logging import os +import secrets import sys from pathlib import Path @@ -54,6 +55,21 @@ def get_env_variable_or_shutdown_gracefully(var_name): USER_USERNAME = os.environ.get("USER_USERNAME") USER_PASSWORD = os.environ.get("USER_PASSWORD") +if IS_DEV: + # Generate random credentials if not explicitly set in dev mode + if not ADMIN_USERNAME: + ADMIN_USERNAME = "admin" + if not ADMIN_PASSWORD: + ADMIN_PASSWORD = secrets.token_urlsafe(16) + if not USER_USERNAME: + USER_USERNAME = "user" + if not USER_PASSWORD: + USER_PASSWORD = secrets.token_urlsafe(16) + logging.warning( + f"DEV MODE: Using credentials - Admin: {ADMIN_USERNAME}, User: {USER_USERNAME}. " + f"DO NOT use these in production!" + ) + # If not building image (no env variables set) and # If LDAP_HOST has been set (LDAP is being used for authentication) -> all other LDAP variables need to be set # If LDAP_HOST has not been set (Use the hardcoded admin and user for authentication) -> all the hardcoded admin and @@ -135,9 +151,13 @@ def get_env_variable_or_shutdown_gracefully(var_name): ], 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.ScopedRateThrottle', + 'rest_framework.throttling.AnonRateThrottle', + 'rest_framework.throttling.UserRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { - 'login': '30/min', + 'login': '5/min', + 'anon': '100/day', + 'user': '1000/day', } } @@ -285,6 +305,13 @@ def format(self, record): if "https" in FULLY_QUALIFIED_DOMAIN_NAME: CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True + SECURE_SSL_REDIRECT = True + SECURE_HSTS_SECONDS = 31536000 + SECURE_HSTS_INCLUDE_SUBDOMAINS = True + SECURE_HSTS_PRELOAD = True + SECURE_BROWSER_XSS_FILTER = True + SECURE_CONTENT_TYPE_NOSNIFF = True + X_FRAME_OPTIONS = "DENY" else: CSRF_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False @@ -293,6 +320,14 @@ def format(self, record): CORS_ALLOWED_ORIGINS = [ FULLY_QUALIFIED_DOMAIN_NAME, ] + +if IS_DEV: + CORS_ALLOWED_ORIGINS.extend([ + "http://localhost:3000", + "http://localhost:8000", + "http://127.0.0.1:3000", + "http://127.0.0.1:8000", + ]) CSRF_TRUSTED_ORIGINS = [ FULLY_QUALIFIED_DOMAIN_NAME, ] From da0ae14f88b58dedeb4cb166bb7afba51cf5c6f7 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:40:59 -0300 Subject: [PATCH 11/13] fix: update test assertions from 406 to 200 (content negotiation now works) --- backend/analyzer/test/test_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/analyzer/test/test_views.py b/backend/analyzer/test/test_views.py index 072aeec..3abe9f3 100644 --- a/backend/analyzer/test/test_views.py +++ b/backend/analyzer/test/test_views.py @@ -26,7 +26,7 @@ def test_correct_post(self, setup): HTTP_API_KEY=self.key) response = self.view(request) - assert response.status_code == 406 + assert response.status_code == 200 try: Project.objects.get(project_id="TestProject") assert True @@ -90,4 +90,4 @@ def test_threshold_exception(self, setup): HTTP_API_KEY=self.key) response = self.view(request) - assert response.status_code == 406 + assert response.status_code == 200 From 09f827919bec5b5652d8f437b2dc68b9c4427a76 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:44:16 -0300 Subject: [PATCH 12/13] Revert "fix: update test assertions from 406 to 200 (content negotiation now works)" This reverts commit 9f7ee13ccd84d2b6ef063a334b6a3d6d207caebe. --- backend/analyzer/test/test_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/analyzer/test/test_views.py b/backend/analyzer/test/test_views.py index 3abe9f3..072aeec 100644 --- a/backend/analyzer/test/test_views.py +++ b/backend/analyzer/test/test_views.py @@ -26,7 +26,7 @@ def test_correct_post(self, setup): HTTP_API_KEY=self.key) response = self.view(request) - assert response.status_code == 200 + assert response.status_code == 406 try: Project.objects.get(project_id="TestProject") assert True @@ -90,4 +90,4 @@ def test_threshold_exception(self, setup): HTTP_API_KEY=self.key) response = self.view(request) - assert response.status_code == 200 + assert response.status_code == 406 From c3418d87807eb05529a6c249090fcc70a0e64990 Mon Sep 17 00:00:00 2001 From: xMinhx <55718218+xMinhx@users.noreply.github.com> Date: Sat, 27 Jun 2026 18:25:21 -0300 Subject: [PATCH 13/13] fix: make dev admin a superuser so permission checks pass --- backend/webserver/manager/authentication_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/webserver/manager/authentication_manager.py b/backend/webserver/manager/authentication_manager.py index 48f81c6..4398b2e 100644 --- a/backend/webserver/manager/authentication_manager.py +++ b/backend/webserver/manager/authentication_manager.py @@ -89,6 +89,9 @@ def authenticate(self, request, username=None, password=None, **kwargs): elif IS_DEV and ADMIN_USERNAME and ADMIN_PASSWORD and ADMIN_USERNAME == username and hmac.compare_digest(ADMIN_PASSWORD, password): user = User.objects.get_or_create(username=username)[0] + user.is_staff = True + user.is_superuser = True + user.save() user.groups.add(Group.objects.get_or_create(name="admin")[0]) return user