Skip to main content

xlsx-provenance

Status Python License Last Commit

Fingerprint .xlsx files for authorship and authenticity. Tells you whether a workbook was actually authored in Excel, or generated by a library (openpyxl, xlsxwriter, Aspose, ClosedXML, EPPlus, SheetJS, ...) or another office suite (LibreOffice, OnlyOffice, WPS, ...).

Stdlib-only Python. No third-party dependencies.

Why

An .xlsx is a zip of XML. Every authoring tool leaves fingerprints: different Application strings, different fileVersion attributes, different presence/absence of calcChain.xml, theme1.xml, printerSettings*.bin, <HeadingPairs>, <TitlesOfParts>, cellXfs count="" attributes, and so on.

This tool reads those fingerprints and gives you a per-file verdict you can act on.

Install

pipx install xlsx-provenance        # or: uv tool install xlsx-provenance
brew install jtannahill/tap/xlsx-provenance

Or run straight from a checkout (no install, stdlib only):

git clone https://github.com/jtannahill/xlsx-provenance.git
./xlsx-provenance/xlsx-provenance some-file.xlsx

Editing metadata

The same tool can rewrite the declared metadata, either to scrub a file before sharing it or to set the properties you want.

xlsx-provenance --strip report.xlsx                      # blank author, company, dates, application... in place
xlsx-provenance --strip --backup report.xlsx             # same, keeping report.xlsx.bak
xlsx-provenance --strip -o clean.xlsx report.xlsx        # write to a new file
xlsx-provenance --set creator="Jane Doe" --set company=Acme --set created=2024-01-02 report.xlsx
xlsx-provenance --strip --set creator="Jane Doe" report.xlsx   # scrub, then set just one
xlsx-provenance --list-properties                        # keys accepted by --set

Only docProps/core.xml and docProps/app.xml are rewritten. Every other zip member is copied byte for byte in its original order, so the workbook stays exactly as valid as it was and the structural provenance signals are untouched. An empty value (--set manager=) removes a property; dates take ISO 8601 and are stored as UTC.

This edits declared metadata. It is not a way to pass a generated file off as Excel-authored: the analyzer requires the workbook body to corroborate a declared Excel application (fileVersion, theme, styles, calcChain...), and a file that claims Excel without that structure is reported as SUSPECT. A stripped file is reported with a metadata=stripped signal.

Usage

xlsx-provenance file1.xlsx [file2.xlsx ...]
xlsx-provenance *.xlsx
xlsx-provenance --json file.xlsx           # machine-readable
xlsx-provenance -v file.xlsx               # show full signal breakdown
xlsx-provenance -q *.xlsx                  # one line per file
xlsx-provenance --no-color file.xlsx       # plain output

Exit code is 0 if every file is verdict EXCEL_* or a non-Excel office suite (LIBREOFFICE, ONLYOFFICE, etc.). It is 1 if any file came back from a programmatic library (OPENPYXL, XLSXWRITER, ...) or as SUSPECT / UNKNOWN / MISSING / INVALID. Useful in CI.

Verdicts

Verdict Meaning
EXCEL_MAC Microsoft Macintosh Excel. Authentic Excel for Mac
EXCEL_WIN Microsoft Excel. Authentic Excel for Windows or Online
EXCEL_OTHER Some other Excel variant string, but Excel fileVersion confirmed
EXCEL_LIKELY No explicit Application, but enough Excel-only artifacts (calcChain, fileVersion appName=xl, real theme, printerSettings, VBA, threaded comments, etc.) to be confident
OPENPYXL, XLSXWRITER, ASPOSE, CLOSEDXML, EPPLUS, OPENXML_SDK, SHEETJS, SPREADJS, SYNCFUSION, GEMBOX, SPIRE, LUCKYSHEET, PYTHON_XLSX Application string explicitly declared a generation library
LIBREOFFICE, ONLYOFFICE, OPENOFFICE, GNUMERIC, CALLIGRA, WPS_OFFICE, APPLE_NUMBERS, GOOGLE_SHEETS Authentic but non-Excel office suite
SUSPECT No Application and no fileVersion. Looks tampered or hand-built
UNKNOWN Couldn't classify
MISSING File doesn't exist
INVALID Not a valid zip / corrupted

Signals examined

  • docProps/app.xml: Application, AppVersion, Company, Manager, DocSecurity, presence of HeadingPairs + TitlesOfParts (Excel-only, openpyxl skips)
  • docProps/core.xml: creator, lastModifiedBy, created / modified timestamps (and their delta; < 1s smells automated), lastPrinted
  • xl/workbook.xml: <fileVersion appName="xl" rupBuild="..."> (Excel-only), workbookPr/@codeName, defined names, sheet count
  • Zip artifacts: calcChain.xml (Excel writes, libraries usually skip), theme/theme1.xml size (Excel: ~6796–8390 B; openpyxl: < 4 KB), printerSettings*.bin, vbaProject.bin, pivotTables/, pivotCache/, connections.xml, externalLinks/, charts/, drawings/, comments*.xml, threadedComments (Excel 365), tables/, queryTables/
  • xl/styles.xml: Excel often omits count="N" on <cellXfs>; openpyxl always includes it. Excel writes <tableStyles>, <indexedColors>.
  • xl/sharedStrings.xml: uniqueCount attribute presence
  • [Content_Types].xml: number of overrides, presence of theme override

The verdict combines a hard match on the Application string with a soft score (0–14) over the structural signals. High score with no library declaration → EXCEL_LIKELY.

Examples

Pretty (default):

=== model.xlsx ===
  [EXCEL_MAC] Excel signal score 11/14  (confidence: high)
  application    Microsoft Macintosh Excel  /  AppVersion 16.0300
  identity       creator=''  lastModifiedBy='james tannahill'
  fileVersion    appName='xl' lastEdited='7' lowestEdited='7' rupBuild='10503'
  workbook       sheets=11  definedNames=0  codeName=None
  artifacts      calcChain, theme1(6798B), printerSettings, drawings
  styles.xml     count attr omitted, tableStyles, indexedColors
  content-types  18 overrides, theme=True
  zip            22 entries

Library tell:

=== generated.xlsx ===
  [OPENPYXL] Application explicitly declares openpyxl  (confidence: high)
  application    Microsoft Excel Compatible / Openpyxl 3.1.5  /  AppVersion 3.1
  identity       creator=''  lastModifiedBy='someone'
  fileVersion    <missing>
  ...

JSON for piping into other tooling:

xlsx-provenance --json *.xlsx | jq '.[] | select(.verdict == "OPENPYXL") | .path'

How to "fix" an openpyxl-generated file

Open it in real Excel and File → Save As (overwrite or new name). Excel rewrites every metadata field (Application, fileVersion, calcChain, theme) to its native fingerprint. Editing docProps/app.xml by hand only fixes the visible Application string and leaves the deeper structural tells intact.

License

MIT

Metadata

Release files for xlsx-provenance 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xlsx-provenance 1.1.0
File Size Uploaded
xlsx_provenance-1.1.0.tar.gz 14.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xlsx-provenance 1.1.0
File Interpreter ABI Platform
xlsx_provenance-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.7 kB

Release files / xlsx_provenance-1.1.0.tar.gz

Download URL xlsx_provenance-1.1.0.tar.gz
Size 14.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b12f4fb06ff2b10a72f6a2f519b01bd0b3f36a2396108b3fab05ad551b9bc31f
BLAKE2b-256 checksum
How to use checksums
6e809a9b804f213db55e8900ad4b766e7d9510ac3a80674bbf5703a56214b7c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / xlsx_provenance-1.1.0-py3-none-any.whl

Download URL xlsx_provenance-1.1.0-py3-none-any.whl
Size 14.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
930b42560cb67e4ffcef97ac360754d0611cc1cbd1185021d70b3e6341d039fd
BLAKE2b-256 checksum
How to use checksums
eb4e7cfbce1d2dca47976bc87743961798b46ef5a12270524850939940f2e257
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page