T09 · Insecure Skill Coding Practices
- Location
src/sds_generator/outputs/source_map_csv.py:39- Finding
Spreadsheet Formula Injection in Provenance CSV Output
- Content
View full analysis
Vulnerability Details
File Location:
src/sds_generator/outputs/source_map_csv.py:39-44,src/sds_generator/outputs/source_map_csv.py:99-128, andsrc/sds_generator/outputs/source_map_csv.py:172-185
Vulnerability Type: CSV/spreadsheet formula injection caused by insufficient output neutralization
Risk Level: MediumVulnerable Code
python def _serialize_cell(value: Any) -> str: if value is None: return "" if isinstance(value, list | dict): return json.dumps(value, ensure_ascii=False, sort_keys=True) return str(value)Untrusted source-document values and excerpts are placed into CSV records without formula neutralization:
python records.append( { "field_path": field_path, "field_priority": str(entry.get("priority", "")), "final_value": _serialize_cell(structured_field.value), "display_value": _serialize_cell(structured_field.display_value), "status": structured_field.status.value, "origin_kind": origin_kind.value if origin_kind is not None else "", "selected": bool(selected_row.selected) if selected_row is not None else False, "selection_reason_code": selection_reason_code( field_path=field_path, status=structured_field.status, origin_kind=origin_kind, rows=raw_rows, notes=notes, evidence_required=bool(entry.get("evidence_required")), ), "selection_reason": selection_reason_text( field_path=field_path, status=structured_field.status, origin_kind=origin_kind, selected_row=selected_row, notes=notes, evidence_required=bool(entry.get("evidence_required")), ), "source_file": selected_row.source_file if selected_row is not None else "", "source_profile": selected_row.source_profile.value if selected_row is not None else "", "source_authority": selecte ...[truncated 3847 chars]- Remediation
View remediation
Remediation Suggestions
- Introduce a dedicated serializer that neutralizes every string cell beginning with a dangerous formula prefix after accounting for leading whitespace, tabs, and control characters.
python FORMULA_PREFIXES = ("=", "+", "-", "@") def _csv_safe_cell(value: Any) -> str: serialized = _serialize_cell(value) inspected = serialized.lstrip(" \t\r\n") if inspected.startswith(FORMULA_PREFIXES): return "'" + serialized return serialized-
Apply the safe serializer to every CSV field, not only known evidence fields. This should include filenames, selection reasons, excerpts, normalized values, and derived values.
-
Keep the original unmodified value in JSON if exact machine-readable provenance is required, while treating CSV as a spreadsheet-facing representation.
-
Consider generating an XLSX file with cells explicitly typed as text if spreadsheet compatibility is a core requirement.
-
Add regression tests for values beginning with:
=+-@- Leading spaces followed by a formula marker
- Tabs or carriage returns followed by a formula marker
-
Document that previously generated CSV artifacts should be treated as untrusted when opened in formula-capable applications.
