1
0
Fork 0

feat(inbox): .xlsx extraction — formula gate, hidden sheets, cell comments (stage 2h)

This commit is contained in:
Kjell Tore Guttormsen 2026-07-07 07:41:00 +02:00
commit ca26e117ea
3 changed files with 132 additions and 1 deletions

View file

@ -422,3 +422,87 @@ def test_clean_pptx_grouped_shape_admits(tmp_path):
fp = _pptx_with_grouped_text(tmp_path, "clean grouped text")
_extracted, _result, verdict = receive([fp])
assert verdict == "ADMIT"
# --- slice 2h: .xlsx (openpyxl) ---------------------------------------------
# Three planted regions (PLAN §247). A formula-lead cell (=cmd|'…', =HYPERLINK)
# is a spreadsheet threat the guard would not recognize, so the front-end refuses
# it (mirrors .csv). A hidden sheet and a cell comment hide injection text where a
# human reading the workbook does not look; the extractor surfaces both so the
# stage-2 scan catches them. openpyxl reads formulas as their string, iterates
# hidden sheets, and exposes cell comments (verified empirically).
def _make_xlsx(tmp_path, name="book.xlsx", *, cell="A normal value",
formula=None, hidden_sheet=None, comment=None):
from openpyxl import Workbook
from openpyxl.comments import Comment
wb = Workbook()
ws = wb.active
ws["A1"] = cell
if formula is not None:
ws["A2"] = formula # leading '=' -> stored as a formula
if comment is not None:
ws["A1"].comment = Comment(comment, "m")
if hidden_sheet is not None:
hs = wb.create_sheet("secret")
hs.sheet_state = "hidden" # invisible tab in Excel
hs["A1"] = hidden_sheet
fp = tmp_path / name
wb.save(str(fp))
return fp
def test_xlsx_formula_injection_cell_is_flagged(tmp_path):
fp = _make_xlsx(tmp_path, name="data.xlsx", formula="=cmd|'/c calc'!A1")
extracted, _result, verdict = receive([fp])
assert extracted.provenance[0].source_type == "xlsx"
assert any("data.xlsx" in n for n, _r in extracted.rejected)
assert verdict == "REJECT"
def test_xlsx_hyperlink_formula_is_flagged(tmp_path):
fp = _make_xlsx(tmp_path, formula="=HYPERLINK('http://evil')")
extracted, _result, verdict = receive([fp])
assert extracted.rejected != ()
assert verdict == "REJECT"
def test_xlsx_hidden_sheet_injection_is_caught(tmp_path):
fp = _make_xlsx(tmp_path, hidden_sheet=_INJECTION)
_extracted, result, verdict = receive([fp])
assert result.disposition is Disposition.FAIL_SECURE # caught by the guard
assert verdict == "REJECT"
def test_xlsx_cell_comment_injection_is_caught(tmp_path):
fp = _make_xlsx(tmp_path, comment=_INJECTION)
_extracted, result, verdict = receive([fp])
assert result.disposition is Disposition.FAIL_SECURE
assert verdict == "REJECT"
def test_clean_xlsx_admits(tmp_path):
fp = _make_xlsx(tmp_path, name="clean.xlsx", cell="A routine value. No behavior change.")
extracted, _result, verdict = receive([fp])
assert "uploads/clean.md" in extracted.bundle
assert extracted.rejected == ()
assert verdict == "ADMIT"
def test_xlsx_formula_detach_proof(tmp_path):
# The same workbook with a plain cell (no formula lead) has no flag -> ADMIT,
# so the flag is the formula content, not the .xlsx suffix or the filename.
fp = _make_xlsx(tmp_path, name="data.xlsx", formula="calc")
extracted, _result, verdict = receive([fp])
assert extracted.rejected == ()
assert verdict == "ADMIT"
def test_xlsx_hidden_sheet_detach_proof(tmp_path):
# Same visible cell, no hidden sheet -> ADMIT: it is the extractor surfacing the
# hidden-sheet region that caught it, not merely "an xlsx".
fp = _make_xlsx(tmp_path, cell="A routine value.")
_extracted, _result, verdict = receive([fp])
assert verdict == "ADMIT"