Threat model¶
doormat reads spreadsheets that may come from anyone and reports the secrets it finds. This page states what it protects, against whom, how, and what it does not protect. It describes version 0.2.
Assets¶
- The host process. doormat runs inside your Python process, often in a scanner that processes many files. A file must not be able to crash it, hang it, exhaust its memory or run code in it.
- The secrets it finds. A finding contains a live credential. doormat must not spread it further than the caller asked.
- The integrity of results. A scan is used to make decisions (block a commit, open an incident). Results must be correct about where a secret is.
Adversary¶
The main adversary is the author of a spreadsheet being scanned. They control every byte of the file: zip structure, XML, cell contents, declared sizes and ranges. They may want to disrupt the scanner (denial of service) or hide a secret from it (evasion).
doormat does not defend against an adversary who controls the Python environment, the doormat package itself, or its dependencies; those are supply-chain risks covered by the project's dependency policy instead.
Parser surface by format¶
| Format | Parsed by | Container | Notes |
|---|---|---|---|
.xlsx, .xlsm |
openpyxl | zip | XML parsed through defusedxml |
.ods |
odfpy | zip | odfpy parses XML through defusedxml itself |
.xls |
xlrd | OLE2 compound file | binary, no XML |
.csv, .tsv |
Python csv |
plain text | no container, no XML |
doormat's own detection code is Rust with no unsafe in the bindings; it
receives already-extracted cell text.
Attacks and mitigations¶
| Attack | Mitigation | Status |
|---|---|---|
| XML entity expansion ("billion laughs"), external entities (XXE) | defusedxml refuses entity declarations in .xlsx and .ods XML. A worksheet that declares entities is skipped and listed in GridDoc.errors; anywhere else load() raises ValueError |
Mitigated, tested |
| Zip bomb (tiny file, huge decompressed parts) | Before parsing, the zip directory is checked: more than 10,000 parts, more than 512 MiB declared (configurable), or a part of 10 MiB or more compressed beyond 500:1 is refused | Mitigated for honest headers, tested with real bombs |
Range inflation (one formatted cell at XFD1048576, about 17 billion positions) |
The .xlsx extractor visits only cells present in the file, never the declared range |
Mitigated, tested |
Repeat inflation in .ods (a cell "repeated" a billion times) |
Repeated empty cells are skipped; repeated values are capped at 16,384 columns and rows at 1,000 repetitions | Mitigated, tested |
| Secret leaking into logs | repr(Relationship) omits the value; doormat logs nothing |
Mitigated |
| A malformed sheet hides the rest of the workbook | Each sheet is parsed separately; one that fails is listed in GridDoc.errors and the others are still scanned |
Mitigated, tested |
| Wrong location reported | Findings carry sheet, row and column from the file; regressions are tested (e.g. repeated ODS rows) | Mitigated |
| Evasion: secret with no label near it | Not detectable by design; doormat finds labelled secrets | Not mitigated |
| Evasion: label in a language or form doormat does not know | Keyword lists are finite | Partially mitigated |
Residual risks¶
- Forged zip sizes. The zip check trusts the sizes declared in the archive directory. A deliberately falsified archive can understate them; the decompressor then produces more data than declared. A Rust-side reader that counts decompressed bytes is planned.
- No time limit. doormat has no per-file deadline. Inputs that are slow but within the limits above (a genuinely huge workbook) can take minutes. Run large-scale scans with a process-level timeout and memory limit.
- Third-party parsers. openpyxl, odfpy and xlrd parse untrusted input in
pure Python. A bug in them is a bug in doormat's attack surface; Dependabot
and
pip-auditwatch for published advisories. - A sheet that cannot be parsed is not scanned.
load()reports it inGridDoc.errorsand scans the other sheets, so its secrets are missed. Callers that need an exhaustive scan must checkGridDoc.complete. An.odsfile keeps all sheets in one XML document, so damage there still fails the whole file.
Recommendations for deployment¶
- Treat findings as secrets: store and transmit them as you would the
credentials themselves, and avoid printing
Relationship.value. - Scan untrusted files in a separate process with a timeout and memory limit.
- Report suspected vulnerabilities privately, as described in the security policy.