Trust
Security and architecture
BillDelta reads files a third party produced, on a machine you control, and stores the result locally. This page describes what that means in practice, and where the boundaries are.
Describes BillDelta 0.9.0 · engine 1.2.0-m1 · persistence schema 7
The shape of the product
BillDelta is a Windows desktop application. There is no server component, no account system, no synchronisation and no multi-tenant database, because there is no cloud service for one to belong to.
The reconciliation path contains no network call, no telemetry, no analytics and no remote asset. This is not a preference in a settings pane. The application window is denied network access outright at the browser-engine level, and that denial is asserted by the packaged smoke test rather than assumed.
Your invoice, your rate card and your operational exports are read from wherever they already sit on disk. Canonical records and completed audit runs live in a SQLite file in your own user profile. Exports are written where you choose to write them.
We do not say your data is encrypted. Your operating system may encrypt the disk it sits on; that is a property of your machine, not of this software, and claiming it would be claiming someone else’s control as our own.
The privacy boundary
Your order and shipment exports contain customer personal data. An invoice audit has no operational reason to know who a parcel went to, so the product is built so that the information has nowhere to be stored.
1. A canonical-field allowlist
A column reaches storage only by being mapped to one of a fixed list of named operational fields. That list holds references, dates, quantities, units and money. It holds no customer name, e-mail address, telephone number, street address, city or postcode. Everything else is dropped while the row is still a transient array of strings — not written to the database, not held in a view model, not retained as a parsed blob.
It is an allowlist rather than a blocklist because enumerating the fields an audit needs is tractable, while enumerating every column name a warehouse management system might invent for a phone number is not.
2. Header matching, as a second line
A mapping that would pull personal data into a canonical field is refused, and dropped columns are named in the import report so you can see your customer names were discarded rather than merely ignored. There is no override, which also means there is no “keep the personal data” code path for a reviewer to audit.
The matching is two-tier on purpose. A strong signal — Customer Email, Shipping Address1, Billing Name, Phone — blocks whatever it is mapped to. A weak signal — a bare Name, City, Company — blocks only free-text fields, because Shopify’s order export calls the order number Name and rejecting the single most common file this product will ever read would make the control useless rather than merely annoying.
How it is proved
The product’s own test suite runs the whole pipeline over fixtures containing synthetic customer names, addresses, e-mail addresses and telephone numbers, writes a real database file to disk, then reads that file back as raw bytes and searches it — including any write-ahead log or journal. Querying the tables would only prove the columns someone thought to check are clean; scanning the file proves nothing leaked into a JSON payload, an index or a free-text column either. The suite first asserts the fixtures still contain the values, so it cannot pass by quietly losing its own test data.
Third-party files are treated as hostile
A provider invoice is a file produced by someone else’s billing system, and a charge description is attacker-controlled text. The parsers are first-party because their behaviour is product behaviour.
- CSV. Caps on rows, columns and cell length. Blank and duplicate headers, misplaced quotes and unterminated quoted values are rejected. An ambiguous delimiter is refused rather than guessed, so a semicolon-delimited European export is never silently read as one column. Files are decoded as UTF-8 strictly; anything else is a specific, actionable error rather than replacement characters flowing into charge codes.
- XLSX. Accepted only after the package structure validates, with limits on compressed size, per-entry and aggregate expansion, expansion ratio, entries, sheets, rows, columns, cells, shared strings, styles and merges. Macros, ActiveX and embedded objects, external connections and external relationships are refused. There is no formula evaluator: formula text is never used as a cell value, external workbooks and URLs are never fetched, and hyperlink relationships are metadata that is never opened.
- Formula injection on the way out. A cell beginning
=,+,-,@, a tab or a carriage return executes when a spreadsheet opens it. On import the value is data and is preserved exactly; on export it is prefixed so Excel and Sheets treat it as literal text. The value is marked, never rewritten. - HTML. Every interpolation in the dispute pack is escaped. No path renders raw markup from a description or a review note.
- Your original file is not copied. It is hashed through a bounded byte stream and read where it lies. Extracted spreadsheet XML is never written to disk.
The application boundary
The product is an Electron application and its window is treated as untrusted — not because a bug is expected in it, but because a security argument that depends on the window behaving is not an argument.
- Node integration is off in the window, in workers and in sub-frames.
- Context isolation and the OS sandbox are on.
- The window is denied the network entirely. Navigation to any other origin is refused and every new-window request is denied.
- Device, permission and permission-check handlers all return false. Embedded web views are disabled. Developer tools are off in a packaged build.
- There is one validated channel. Every request is a name-and-payload envelope; the host checks the sender, resolves the name against an allowlist, and re-validates the payload before any privileged code runs.
- The window is never given a filesystem path. The native file dialog runs in the host process, keeps the path, and hands back an opaque one-shot handle and a display name. A handle the host did not mint is refused, and a handle is spent when it is redeemed — so there is no string the window can send that names a file you did not choose.
Spreadsheet parsing runs in a disposable utility process rather than in the host, and parser exceptions are mapped to fixed codes before they cross the boundary.
Logs and errors say nothing about your machine
A database constraint message and a permission error naming a path under your user profile are both things this code could produce, and neither belongs in a window that might be screenshotted into a support thread. Unexpected failures become a generic error with a fixed sentence; the local log keeps a code and a short developer detail, never the message, the payload or the row.
Import failures take the more useful path: an exact total plus a bounded set of physical row, canonical field, stable error code and safe explanation. Values appear only for allowlisted canonical fields and are length-bounded. Dropped personal-data columns are never diagnostic payloads.
Release integrity
The current early-access deliverable is a portable Windows x64 ZIP containing the packaged application directory. It is not an installer, and it carries its own runtime — no checkout, no Node.js, no package manager and no external database after build.
The early-access build is not code-signed. Windows SmartScreen will warn when you run it. Procuring a Windows code-signing certificate and executing the documented signing procedure is a known, outstanding release task — it is not something this page is going to imply has already happened.
Each release artefact is published with a SHA-256 manifest so the ZIP you were sent can be checked against the digest the build recorded before you extract it.
Explicitly out of scope
Stated so that nothing here is read as implying otherwise. The product’s threat model does not cover:
- An attacker who already has your Windows account.
- Disk encryption. That is your operating system’s job, and we do not claim it.
- Multi-user access control. There is one workspace and one user, by design.
- Code signing of the current release, which is outstanding and tracked as a release task.
The loopback development host on port 4319 exists for building the interface and is not packaged. Production depends on no local server, no external service and no network of any kind.
Reporting a problem
If you believe you have found a security issue, write to dogantunahan.tr@gmail.com with enough detail to reproduce it. Please do not open a public issue first.