doormat
Find the passwords people leave next to the label that says "Password".
People hide the spare key under the doormat. They hide passwords the same way: in the spreadsheet cell right next to Password:, in a cell that reads pwd = hunter2, or inside a database connection string. doormat reads .xlsx, .xls, .ods, .csv and .tsv files, models each sheet as a 2D grid, and pairs credential labels with the values around them.
- Spatial, not just regex. Scores every candidate value by its distance and direction from a credential label, its character mix and entropy, and nearby username or URL labels.
- Finds what grep misses. Credentials split across cells, built with
CONCATin formulas, or tucked into cell comments. - Fast. Rust core with sheet-level parallelism: under 1 ms for a 5,000-cell sheet. Python bindings via PyO3, one FFI call per workbook.
- Measured on real data. Precision and recall are reported on the public Enron spreadsheet corpus, not just synthetic fixtures. See Accuracy.
- Offline and dependency-light. Two small runtime dependencies (
openpyxl, anddefusedxmlto refuse malicious XML inside spreadsheet files). No network calls, no model downloads.
Documentation: https://dipan010.github.io/doormat/ (API reference, how detection works, threat model, evaluation).
Installation
pip install doormat
Optional readers for legacy formats:
pip install "doormat[xls]" # Excel 97-2003 via xlrd
pip install "doormat[ods]" # OpenDocument via odfpy
pip install "doormat[all]" # both
Prebuilt wheels cover CPython 3.10 and newer on Linux (x86_64, aarch64), macOS (Intel, Apple Silicon) and Windows (x64). On other platforms pip builds from the source distribution, which needs a Rust toolchain. To work on doormat itself, see CONTRIBUTING.md.
Quickstart
import doormat
doc = doormat.load("it_handover.xlsx")
print(f"Scanned {doc.sheet_count} sheets, {doc.cell_count} cells")
for cred in doc.credentials(min_confidence=120):
print(f"{cred.sheet}!{cred.coordinate} {cred.key!r} -> {cred.value!r} score={cred.confidence:.0f}")
Scanned 2 sheets, 7 cells
Servers!B3 'Password' -> 's3cRet!99' score=210
Notes!A1 'VPN password' -> 'hunter2!' score=250
Findings come back in reading order: sheet by sheet, then row and column. A finding's repr never includes the secret, so logging one is safe:
>>> doc.credentials(120)[0]
Relationship(key='Password', sheet='Servers', cell='B3', confidence=210)
API
| Name | Description |
|---|---|
doormat.load(path, *, max_uncompressed_bytes=512 MiB) |
Read a spreadsheet (str or pathlib.Path) and run detection. Returns a GridDoc. Raises FileNotFoundError, or ValueError for an unsupported extension, a file whose signature does not match it, or a zip-based file that looks like a zip bomb. max_uncompressed_bytes=None lifts the size limit. |
GridDoc.credentials(min_confidence=0.0) |
Relationships at or above a confidence score. 120 is a sensible default; scores above 200 are high-confidence inline or formula detections. |
GridDoc.relationships() |
Every inferred key/value relationship, regardless of score. |
GridDoc.sheet_count, GridDoc.cell_count |
Size of the scanned workbook (sheets that were read). |
GridDoc.errors, GridDoc.complete |
A sheet that cannot be parsed does not stop the scan: it is listed in errors as a SheetError(sheet, kind, message) and the other sheets are still scanned. complete is True when every sheet was read; check it when a scan must be exhaustive. |
Relationship |
Frozen dataclass: key, value, confidence, reason (a ;-separated breakdown of the score), and where it was found: sheet, row, col (1-based), header_row, header_col and hidden (the sheet is hidden). |
Relationship.coordinate, .header_coordinate |
Spreadsheet references of the value and label cells, e.g. "B3" and "A3". |
What it detects
| Pattern | Example |
|---|---|
| Value beside a label | Password: in A1, the value in B1 or A2 (within a 3-cell radius) |
| Inline key/value | Password: s3cret!, PWD=s3cret!, Password - s3cret! |
| Split across cells | A value spread over 2 to 5 cells under a header |
| Built in a formula | =CONCAT("password", "S3cure#1"), or a password: ... string literal in a formula |
| Hidden in a comment | A comment on a password header, or a comment containing password: ... |
| Multilingual labels | Password keywords in 15+ languages, including German, Spanish, French, Portuguese, Russian, Japanese, Chinese and Korean |
Supported formats
| Format | Extensions | Formulas | Comments | Extra |
|---|---|---|---|---|
| Excel (OOXML) | .xlsx, .xlsm |
yes | yes | none |
| Excel 97-2003 | .xls |
no | no | doormat[xls] |
| OpenDocument | .ods |
yes | yes | doormat[ods] |
| Delimited text | .csv, .tsv |
n/a | n/a | none |
The reader is chosen by file extension, and binary formats are checked against their magic bytes before parsing. CSV and TSV files are treated as one sheet named after the file.
Accuracy
On 14 synthetic fixtures, one per detection pathway, doormat scores 100% precision and recall. Real spreadsheets are harder. On the public Enron spreadsheet corpus (15,929 files):
| Version | Precision | Recall |
|---|---|---|
| 0.1.0 | 16.5% | 31.2% |
current main |
59.3% | 34.7% |
Precision for main is measured on files that played no part in tuning; recall is measured on files that did, so treat it as optimistic. Inline credentials are found with few false positives. Values beside a label are usually found, but a large share of those findings are still wrong pairings (usually a nearby label when the password cell is empty). The method, labelling rubric and caveats are in bench/corpus/README.md.
Performance
Rust core only, Apple Silicon, criterion.rs on synthetic workbooks:
| Workbook | Time |
|---|---|
| 1 sheet, 1,000 cells | 138 µs |
| 1 sheet, 5,000 cells | 695 µs |
| 10 sheets, 5,000 cells each | 2.6 ms |
On very large real workbooks, reading the file with openpyxl dominates: about 27 s to read a 170,000-cell workbook against 0.1 s of detection.
How it works
- Extract. Python reads every cell, formula and comment in a single pass and sends the workbook to Rust in one call.
- Featurize. Each cell is normalized once and tagged with bitmask flags (character classes, header keywords via Aho-Corasick) and Shannon entropy.
- Select candidates. Cells with header keywords, high entropy, formulas, comments or inline patterns are kept and classified as headers or values.
- Group. Adjacent candidates are clustered into regions with a breadth-first search over a 7x7 neighbourhood.
- Pair and score. Each credential label is paired with its best-scoring neighbour using distance, character mix, entropy, region membership and nearby username or URL labels. Labels, prose and formulas are penalized.
Sheets are processed in parallel with Rayon. Scoring is heuristic; there is no trained model.
Limitations
- Hostile files are checked, not sandboxed. XML entity attacks are refused, and
.xlsx/.xlsm/.odsfiles that would decompress beyond 512 MiB, or with a zip-bomb compression ratio, are rejected before parsing. Those checks trust the sizes declared inside the archive, which a determined attacker can forge; run doormat with a memory limit when scanning files from untrusted sources at scale. - Password tables are not detected. A
Passwordcolumn with one credential per row is the most common pattern doormat misses today. - Legacy
.xlsfiles expose no formulas or comments through xlrd, so those pathways only apply to.xlsx,.xlsmand.ods. - Python only. Bindings for other languages are planned, as is a command-line tool.
Roadmap
- 0.2.0: first PyPI release
- 0.3.0: password-table detection, fewer false positives on empty form fields, faster extraction of large workbooks
All planned work is tracked in milestones; changes are listed in CHANGELOG.md.
Contributing
Bug reports and pull requests are welcome. Please follow the Code of Conduct. See CONTRIBUTING.md for building from source, running the tests and the benchmark harness, and adding a detection pathway.
Security
Please report vulnerabilities privately, as described in SECURITY.md. Never attach spreadsheets containing real credentials to issues; rebuild the layout with made-up values.
Acknowledgements
Real-world evaluation uses the Enron spreadsheet corpus by Felienne Hermans and Emerson Murphy-Hill ("Enron's Spreadsheets and Related Emails: A Dataset and Analysis", ICSE 2015), licensed CC BY 4.0. The corpus is not redistributed in this repository.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in doormat by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Metadata
Release files for doormat 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| doormat-0.2.0.tar.gz | 65.7 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| doormat-0.2.0-cp310-abi3-win_arm64.whl | CPython 3.10 | abi3 | Windows ARM64 | Details |
| doormat-0.2.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| doormat-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ x86-64 | Details |
| doormat-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ ARM64 | Details |
| doormat-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| doormat-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| doormat-0.2.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| doormat-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 8.5 MB
Release files / doormat-0.2.0.tar.gz
| Download URL | doormat-0.2.0.tar.gz |
|---|---|
| Size | 65.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5e0607b0d0706f3c1d121375268f285087c21d3d0c728af6f5961dc538cdf39
|
|
BLAKE2b-256 checksum How to use checksums |
3859be3b49972028504fa188060d402ac4716a220dd648680c11de50e8915de1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-win_arm64.whl
| Download URL | doormat-0.2.0-cp310-abi3-win_arm64.whl |
|---|---|
| Size | 798.8 kB |
| Tags | CPython 3.10 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
a1aad49f23faf2865faab67b8e026f670806f2ddbbbfc8993170713f6cde029c
|
|
BLAKE2b-256 checksum How to use checksums |
470ba1ff7ce238507eeb938d9ce3c6aee7aed42f0f0df27ef046cffea2bf1404
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-win_amd64.whl
| Download URL | doormat-0.2.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 851.1 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
9e1aa4f8b4fc3d9caa665a9ea842bfd4fd6d3ace648388aecc395efb90c765bd
|
|
BLAKE2b-256 checksum How to use checksums |
0051f2696d4cb485dbbc073c7d1695ce080fc814457a916512a7ae5daced8c62
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | doormat-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
03a395ca578f5a4e94f8a0461208ab986ba366553fea3f8d5028ae95df823e90
|
|
BLAKE2b-256 checksum How to use checksums |
74145a7a4ec4d4d837cd3ac851c820341f8e34010bc9add183f7ff9641aa699c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl
| Download URL | doormat-0.2.0-cp310-abi3-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.3 MB |
| Tags | CPython 3.10 Linux musl 1.2+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
cdaf8bd2f6321a689dfaba4a6c8038728b7f17ea1a86778d73f51cbdd42b8d09
|
|
BLAKE2b-256 checksum How to use checksums |
0afabf73554a8a025d1b71ee0a254319a2ec84ea5fcc58991c9132991544a268
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | doormat-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3ff9e3ed65b366f878c2de4d4111769c70b5fb4c43b9a9146920dbca4927a3d5
|
|
BLAKE2b-256 checksum How to use checksums |
1080867ada4c3fca51c82fe29e725ab49c2fef9e9248296fbe7054634728fb5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | doormat-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
05e0769f4e7af01447f73b1b860a474bc03eca05b734d626a8ca299d056b2a77
|
|
BLAKE2b-256 checksum How to use checksums |
7e504d589a3388d13e6a02bb5a12b3e023273c8b07a14ef23b8a171cf1271c41
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | doormat-0.2.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 949.9 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
897d70ffb5b5aab49f7dc84186e2357da4ae480040ebd949d927b2efbb71d8f0
|
|
BLAKE2b-256 checksum How to use checksums |
341b182792060faf0d26eab819158834ef68be321ae48e6b2e3354d8eac8736e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / doormat-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | doormat-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 987.5 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
84127f024a762874b78c539de17f7b9d4a6f2be6cddfa780e6f6b5ece3f27c76
|
|
BLAKE2b-256 checksum How to use checksums |
12bb17b99c018e6ec5e1ca16cd64c40e9fd1321686b5b083e0242a067e358834
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency log