mirror of
https://github.com/makeplane/plane.git
synced 2026-02-24 12:11:39 +01:00
[SECUR-105] fix: csv injection vulnerability sanitization #8611
This commit is contained in:
@@ -49,6 +49,7 @@ from plane.bgtasks.workspace_seed_task import workspace_seed
|
||||
from plane.bgtasks.event_tracking_task import track_event
|
||||
from plane.utils.url import contains_url
|
||||
from plane.utils.analytics_events import WORKSPACE_CREATED, WORKSPACE_DELETED
|
||||
from plane.utils.csv_utils import sanitize_csv_row
|
||||
|
||||
|
||||
class WorkSpaceViewSet(BaseViewSet):
|
||||
@@ -81,12 +82,14 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
|
||||
def create(self, request):
|
||||
try:
|
||||
(DISABLE_WORKSPACE_CREATION,) = get_configuration_value([
|
||||
{
|
||||
"key": "DISABLE_WORKSPACE_CREATION",
|
||||
"default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
|
||||
}
|
||||
])
|
||||
(DISABLE_WORKSPACE_CREATION,) = get_configuration_value(
|
||||
[
|
||||
{
|
||||
"key": "DISABLE_WORKSPACE_CREATION",
|
||||
"default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if DISABLE_WORKSPACE_CREATION == "1":
|
||||
return Response(
|
||||
@@ -369,7 +372,7 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
|
||||
"""Generate CSV buffer from rows."""
|
||||
csv_buffer = io.StringIO()
|
||||
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
|
||||
[writer.writerow(row) for row in rows]
|
||||
[writer.writerow(sanitize_csv_row(row)) for row in rows]
|
||||
csv_buffer.seek(0)
|
||||
return csv_buffer
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.analytics_plot import build_graph_plot
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.csv_utils import sanitize_csv_row
|
||||
|
||||
row_mapping = {
|
||||
"state__name": "State",
|
||||
@@ -180,7 +181,7 @@ def generate_csv_from_rows(rows):
|
||||
"""Generate CSV buffer from rows."""
|
||||
csv_buffer = io.StringIO()
|
||||
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
|
||||
[writer.writerow(row) for row in rows]
|
||||
[writer.writerow(sanitize_csv_row(row)) for row in rows]
|
||||
return csv_buffer
|
||||
|
||||
|
||||
|
||||
23
apps/api/plane/utils/csv_utils.py
Normal file
23
apps/api/plane/utils/csv_utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# CSV utility functions for safe export
|
||||
|
||||
# Characters that trigger formula evaluation in spreadsheet applications
|
||||
_CSV_FORMULA_TRIGGERS = frozenset(("=", "+", "-", "@", "\t", "\r", "\n"))
|
||||
|
||||
|
||||
def sanitize_csv_value(value):
|
||||
"""Sanitize a value for CSV export to prevent formula injection.
|
||||
|
||||
Prefixes string values starting with formula-triggering characters
|
||||
with a single quote so spreadsheet applications treat them as text
|
||||
instead of evaluating them as formulas.
|
||||
|
||||
See: https://owasp.org/www-community/attacks/CSV_Injection
|
||||
"""
|
||||
if isinstance(value, str) and value and value[0] in _CSV_FORMULA_TRIGGERS:
|
||||
return "'" + value
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_csv_row(row):
|
||||
"""Sanitize all values in a CSV row."""
|
||||
return [sanitize_csv_value(v) for v in row]
|
||||
@@ -9,6 +9,9 @@ from typing import Any, Dict, List, Type
|
||||
|
||||
from openpyxl import Workbook
|
||||
|
||||
# Module imports
|
||||
from plane.utils.csv_utils import sanitize_csv_row
|
||||
|
||||
|
||||
class BaseFormatter:
|
||||
"""Base class for export formatters."""
|
||||
@@ -84,7 +87,7 @@ class CSVFormatter(BaseFormatter):
|
||||
buf = io.StringIO()
|
||||
writer = csv.writer(buf, delimiter=",", quoting=csv.QUOTE_ALL)
|
||||
for row in data:
|
||||
writer.writerow(row)
|
||||
writer.writerow(sanitize_csv_row(row))
|
||||
buf.seek(0)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ from typing import Any, Dict, List, Union
|
||||
from openpyxl import Workbook, load_workbook
|
||||
|
||||
|
||||
# Module imports
|
||||
from plane.utils.csv_utils import sanitize_csv_row, sanitize_csv_value
|
||||
|
||||
|
||||
class BaseFormatter(ABC):
|
||||
@abstractmethod
|
||||
def encode(self, data: List[Dict]) -> Union[str, bytes]:
|
||||
@@ -128,11 +132,12 @@ class CSVFormatter(BaseFormatter):
|
||||
|
||||
# Write data rows in the same field order
|
||||
for row in data:
|
||||
writer.writerow([row.get(key, "") for key in fieldnames])
|
||||
writer.writerow(sanitize_csv_row([row.get(key, "") for key in fieldnames]))
|
||||
else:
|
||||
writer = csv.DictWriter(output, fieldnames=fieldnames, delimiter=self.delimiter)
|
||||
writer.writeheader()
|
||||
writer.writerows(data)
|
||||
for row in data:
|
||||
writer.writerow({k: sanitize_csv_value(row.get(k, "")) for k in fieldnames})
|
||||
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user