diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py
index 88fe5242ce..e3ecc8b050 100644
--- a/backend/api/projects/resources.py
+++ b/backend/api/projects/resources.py
@@ -575,8 +575,8 @@ def setup_search_dto(request) -> ProjectSearchDTO:
if request.query_params.get("basedOnMyInterests") == "true":
search_dto.based_on_user_interests = authenticated_user_id
- except Exception:
- pass
+ except AttributeError as e:
+ logger.warning("Unable to read authenticated user details: {}", e)
mapping_types_str = request.query_params.get("mappingTypes")
if mapping_types_str:
diff --git a/backend/api/system/general.py b/backend/api/system/general.py
index b66513546b..12712e3210 100644
--- a/backend/api/system/general.py
+++ b/backend/api/system/general.py
@@ -255,8 +255,9 @@ async def release(db: Database = Depends(get_db)):
description: Internal server error
"""
response = requests.get(
- "https://api.github.com/repos/hotosm/tasking-manager/releases/latest"
- )
+ "https://api.github.com/repos/hotosm/tasking-manager/releases/latest",
+ timeout=30,
+ ).json()
try:
tag_name = response.json()["tag_name"]
published_date = response.json()["published_at"]
diff --git a/backend/main.py b/backend/main.py
index 6213a65a20..b3bfe44340 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -40,6 +40,22 @@ async def lifespan(app):
docs_url="/api/docs",
)
+ @_app.middleware("http")
+ async def add_security_headers(request: Request, call_next):
+ response = await call_next(request)
+ # Prevents MIME-sniffing
+ response.headers["X-Content-Type-Options"] = "nosniff"
+ # Prevents Clickjacking by restricting framing
+ response.headers["X-Frame-Options"] = "SAMEORIGIN"
+ # Basic Content Security Policy
+ response.headers["Content-Security-Policy"] = (
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' "
+ "'unsafe-inline';"
+ )
+ # Obscures the specific server software version
+ response.headers["Server"] = "Tasking-Manager"
+ return response
+
# Initialize Sentry only if USE_SENTRY is enabled
if settings.USE_SENTRY:
sentry_sdk.init(
diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py
index 08c24335be..0231d04c00 100644
--- a/backend/models/postgis/project.py
+++ b/backend/models/postgis/project.py
@@ -361,7 +361,7 @@ def set_country_info(self):
"Referer": os.environ.get("TM_APP_BASE_URL", "https://example.com"),
}
try:
- response = requests.get(url, headers=headers)
+ response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
country_info = response.json() # returns a dict
if country_info["address"].get("country") is not None:
diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py
index 3e0b56ccae..50f6ebb142 100644
--- a/backend/services/mapping_service.py
+++ b/backend/services/mapping_service.py
@@ -1,5 +1,5 @@
import datetime
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from databases import Database
from fastapi import BackgroundTasks
diff --git a/backend/services/project_search_service.py b/backend/services/project_search_service.py
index 03c485ff62..f9c0322553 100644
--- a/backend/services/project_search_service.py
+++ b/backend/services/project_search_service.py
@@ -915,7 +915,11 @@ async def get_projects_geojson(
project.default_locale,
)
except Exception:
- pass
+ logger.exception(
+ "Failed to load localized project info for project_id={}",
+ project.id,
+ )
+ continue
properties = {
"projectId": project.id,
diff --git a/desktop.ini b/desktop.ini
new file mode 100644
index 0000000000..ca5df89295
--- /dev/null
+++ b/desktop.ini
@@ -0,0 +1,2 @@
+[.ShellClassInfo]
+LocalizedResourceName=@tasking-manager-develop,0
diff --git a/frontend/src/components/header/signUp.js b/frontend/src/components/header/signUp.js
index ddde704fad..850c7db999 100644
--- a/frontend/src/components/header/signUp.js
+++ b/frontend/src/components/header/signUp.js
@@ -88,9 +88,14 @@ const SignupForm = ({ data, setData, step, setStep }) => {
};
const checkFields = () => {
+ if (data.email.length > 254) {
+ setStep({ ...step, errMessage: });
+ return;
+ }
+
const re =
// eslint-disable-next-line no-useless-escape
- /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ /^(?=.{1,254}$)(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(data.email) === false) {
setStep({ ...step, errMessage: });
return;
diff --git a/pyproject.toml b/pyproject.toml
index 85a66e37ff..a74569293d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,3 +76,6 @@ version_scheme = "pep440"
version_provider = "pep621"
update_changelog_on_bump = true
major_version_zero = true
+
+[tool.bandit]
+exclude_dirs = ["tests"]
diff --git a/requirements.txt b/requirements.txt
index 20d1ea53e8..7f2481cef1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@ alembic==1.11.1
APScheduler==3.10.1
bleach==6.0.0
cachetools==5.3.1
+defusedxml
fastapi==0.108.0
GeoAlchemy2==0.14.3
geojson==3.1.0
diff --git a/tests/api/helpers/test_helpers.py b/tests/api/helpers/test_helpers.py
index ab9ec890ff..abdc605813 100644
--- a/tests/api/helpers/test_helpers.py
+++ b/tests/api/helpers/test_helpers.py
@@ -3,7 +3,7 @@
import json
import logging
import os
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from typing import Tuple
from backend.exceptions import NotFound
diff --git a/tests/api/integration/api/tasks/test_resources.py b/tests/api/integration/api/tasks/test_resources.py
index 4f971edc1c..78819513ea 100644
--- a/tests/api/integration/api/tasks/test_resources.py
+++ b/tests/api/integration/api/tasks/test_resources.py
@@ -1,6 +1,6 @@
# tests/api/integration/test_tasks_queries_refactored.py
import base64
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import pytest
from httpx import AsyncClient
diff --git a/tests/api/integration/services/test_mapping_service.py b/tests/api/integration/services/test_mapping_service.py
index e629942e4f..17798ac0a1 100644
--- a/tests/api/integration/services/test_mapping_service.py
+++ b/tests/api/integration/services/test_mapping_service.py
@@ -1,5 +1,5 @@
import datetime
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from unittest.mock import patch
from backend.services.project_service import ProjectService
diff --git a/tests/backend/helpers/test_helpers.py b/tests/backend/helpers/test_helpers.py
index 9764f54501..558bff064a 100644
--- a/tests/backend/helpers/test_helpers.py
+++ b/tests/backend/helpers/test_helpers.py
@@ -1,7 +1,7 @@
import base64
import json
import os
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from typing import Tuple
import geojson
diff --git a/tests/backend/integration/api/system/test_general.py b/tests/backend/integration/api/system/test_general.py
index a9dab2447b..6fbc8159f0 100644
--- a/tests/backend/integration/api/system/test_general.py
+++ b/tests/backend/integration/api/system/test_general.py
@@ -17,7 +17,8 @@ def test_post_banner(self):
url = "/api/v2/system/release/"
response = self.client.post(url)
release = requests.get(
- "https://api.github.com/repos/hotosm/tasking-manager/releases/latest"
+ "https://api.github.com/repos/hotosm/tasking-manager/releases/latest",
+ timeout=30,
).json()
# Assert
self.assertEqual(response.status_code, 201)
diff --git a/tests/backend/integration/api/tasks/test_resources.py b/tests/backend/integration/api/tasks/test_resources.py
index 731e70af8c..21df2583df 100644
--- a/tests/backend/integration/api/tasks/test_resources.py
+++ b/tests/backend/integration/api/tasks/test_resources.py
@@ -1,4 +1,4 @@
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from backend.models.postgis.statuses import TaskStatus, UserRole
from backend.models.postgis.task import Task
diff --git a/tests/backend/integration/services/test_mapping_service.py b/tests/backend/integration/services/test_mapping_service.py
index e8158d433d..1a25a7846a 100644
--- a/tests/backend/integration/services/test_mapping_service.py
+++ b/tests/backend/integration/services/test_mapping_service.py
@@ -1,5 +1,5 @@
import datetime
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
from unittest.mock import patch
from backend.models.postgis.task import TaskStatus