- Location
- equity_scorer.py:657
- Finding
- Unescaped Population Labels Permit Markdown and Spreadsheet-Formula Injection<![CDATA[
## Vulnerability Details
**File Location**: `equity_scorer.py`, lines 657-663, 873-878, 925, 962-967, and 989
**Vulnerability Type**: Output injection through attacker-controlled population labels
**Risk Level**: Medium
### Vulnerable Code
Population labels are inserted directly into Markdown table rows:
```python
pop_rows = []
for pop in sorted(pop_counts.keys()):
count = pop_counts[pop]
pct = count / total * 100
global_pct = GLOBAL_PROPORTIONS.get(pop.upper(), 0) * 100
ratio = pct / global_pct if global_pct > 0 else float("inf")
o_het = obs_het.get(pop, 0)
e_het = exp_het.get(pop, 0)
pop_rows.append(
"| %s | %d | %.1f%% | %.1f%% | %.2fx | %.4f | %.4f |"
% (pop, count, pct, global_pct, ratio, o_het, e_het)
)
```
The resulting report is written without sanitization:
```python
report_path = output_dir / "report.md"
report_path.write_text(report)
```
Population labels are also written directly into CSV output in the VCF pipeline:
```python
pd.DataFrame([
{"population": k, "count": v, "proportion": v / sum(pop_counts.values()),
"obs_het": obs_het.get(k, 0), "exp_het": exp_het.get(k, 0)}
for k, v in sorted(pop_counts.items())
]).to_csv(tables_dir / "population_summary.csv", index=False)
```
The ancestry CSV pipeline has the same CSV output behavior:
```python
pd.DataFrame([
{"population": k, "count": v, "proportion": v / sum(pop_counts.values())}
for k, v in sorted(pop_counts.items())
]).to_csv(tables_dir / "population_summary.csv", index=False)
```
### Technical Analysis
Population labels originate from user-supplied ancestry CSV data, population-map CSV data, or VCF sample-derived values. These labels are treated as trusted display values when constructing Markdown and CSV outputs.
For Markdown reports, characters such as pipes, brackets, parentheses, newlines, and image syntax are not escaped. A crafted label can break the expected table structure and inject arbitrary Markd
...[truncated 2558 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Validate population labels against a strict allowlist where standardized population codes are expected, such as `AFR`, `AMR`, `EAS`, `EUR`, `SAS`, `OCE`, `MID`, and `UNKNOWN`.
2. If arbitrary labels must be supported, reject control characters and enforce reasonable length limits.
3. Escape Markdown-special characters before interpolation. At minimum, escape backslashes, pipes, brackets, parentheses, angle brackets, and line breaks according to the output context.
4. Treat Markdown links and image syntax as unsafe unless explicitly required.
5. Neutralize spreadsheet formulas before CSV export. Prefix text cells beginning with `=`, `+`, `-`, or `@` with a single quote or another documented safe character.
6. Apply CSV neutralization to every user-controlled text column, not only the population field.
7. Consider offering a non-formula data format such as JSON for machine-readable exports.
8. Add regression tests using labels containing Markdown table delimiters, newlines, links, image syntax, and each spreadsheet formula prefix.
9. Document that generated reports and tables contain data-derived labels and should be opened with remote-content loading and spreadsheet formula execution disabled.
]]>