Build1 distinct publisher3 min readUpdated
A bank-statement converter enforcing connect-src 'none' skipped SheetJS and implemented the four files it needed. Then a green 26-test suite missed a CSP failure only the real page could show.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A developer who runs a client-side converter for bank statements added .xlsx support and declined to install SheetJS, on the grounds that a large third-party parser weakens the specific privacy claim the site makes [4][5]. What makes the decision worth reading is the accounting behind it: he enumerated the subset of the format he actually needed, and it was small [6][7].
The site turns bank CSV exports into the file format QuickBooks Desktop accepts, and the guarantee is enforced rather than asserted [1]. Every page ships a Content Security Policy with connect-src 'none', so the browser will not permit the page to make any network request at all [2]. Open the Network tab during a conversion and it stays empty [3]. His objection to the dependency was not bundle size [4]. It was that "your file never leaves the browser, and here is the policy that enforces it" degrades into "and also trust this dependency", which he calls a materially weaker claim for a tool handling people's bank statements [5].
Then the inventory. An .xlsx is a ZIP of XML: xl/workbook.xml lists the sheets, xl/worksheets/sheet1.xml holds the cells, xl/sharedStrings.xml is a deduplicated string pool that cells reference by index, and xl/styles.xml carries the number formats [6]. Reading that needs three capabilities, two of which the browser already provides [7]. Unzipping means walking the ZIP central directory, about forty lines [8]. Decompression is native, via DecompressionStream('deflate-raw') [9]. For the XML he hand-rolled a tag scanner instead of using DOMParser, because his tests run in Node, where DOMParser does not exist, and he wanted one code path rather than two [10]. Net result: two of the three pieces written by hand, one of them replacing something the platform supplies [1].
Dates cost the most care, because Excel does not store dates. A cell holding 5 January 2024 contains the number 45296, and whether that renders as a date depends on the cell's number format, which lives in a different file inside the archive [11]. So the parser reads styles.xml, works out which style indexes correspond to date formats, and checks every numeric cell against that list [12]. Neither failure mode raises an error [13]. Missing a date format leaves a column of five-digit numbers, which at least looks wrong; treating a currency format as a date turns an amount of 45296 into a day in January 2024, which does not [14]. The epoch is 1899-12-30 rather than 1900-01-01, because Excel treats 1900 as a leap year and reserves serial 60 for a 29 February that never existed [15]. Older Mac files use a 1904 epoch flagged by date1904="1" on workbookPr, and missing that attribute puts every date off by four years and a day [16].
The instructive part is the bug. Twenty-six tests against workbooks the suite generates itself, covering date handling, sparse cells, shared strings and sheet selection, all passing [17]. He then dropped a file on the real page and got back one line: Failed to fetch [18]. The cause was collecting the decompression output by wrapping the stream in a Response and calling arrayBuffer(), which Chrome counts as a fetch, which connect-src 'none' blocks, producing what reads like a network error in code with no network [19]. Draining the reader by hand avoids it [20].
Worth watching in your own build: whether the environment your tests run in enforces the constraint your product sells. Node does not enforce CSP, so the green suite could never have caught this [21]. A smaller companion detail is corrupt input, where the writer side of the stream rejects alongside the reader, so without a .catch() on both writer.write() and writer.close() an Uncaught (in promise) lands next to the real error [22].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The offending code wrapped the decompression stream in a Response and called arrayBuffer(); Chrome treats that as a fetch, connect-src 'none' blocks it, and the resulting error reads like a network failure in code that has no network.
The author runs a small site that converts bank CSV exports into the file format QuickBooks Desktop accepts, and the whole thing runs client-side.
Every page of the site ships a Content Security Policy with connect-src 'none', so the browser refuses to let the page make any network request at all.
With the Network tab open during a conversion, it stays empty; the author describes this as the feature rather than an add-on.
When adding Excel support, the obvious choice was SheetJS; the author decided against it, and says the reason was not bundle size.
The author's argument: the claim "your file never leaves the browser, and here is the policy that enforces it" becomes "and also trust this dependency" once a large third-party parser is included, which he calls a materially weaker claim for a tool that handles people's bank statements.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed but wholly self-reported
The account is internally specific and checkable in principle - named archive members, a named API, a reproducible code snippet, a stated test count, and a pointer to unminified source - which is stronger than assertion alone. But the cluster contains exactly one source, authored by the person who built and markets the tool, with no independent reproduction of the Chrome connect-src behaviour and no external review of the privacy claim. The author himself flags that the Firefox and Safari behaviour is unconfirmed.
One self-run deployment, no external uptake
Adoption evidence is limited to the author deploying the parser on his own small converter site and publishing the source unminified. No other users, integrators, downloads, traffic figures, or third-party reports appear in the supplied material, and the technique is described as a one-off in-house implementation rather than a released library.
Scoped conservatively, slightly understated
The framing is narrower than the evidence would allow to be stretched: the author names his limits (no .xls, Zip64 refused, sheet selection an admitted compromise, cross-browser behaviour unconfirmed), reports his own failure prominently, and does not claim general superiority over SheetJS. The generalisable operator lesson - CI that cannot enforce CSP proves nothing about the deploy target - is arguably underplayed relative to its reach, so the account reads mildly understated rather than overstated.
Author markets the product being described
The piece is self-published on dev.to by the operator of qbofile.com and closes by pointing readers at that site's source file. The dependency-refusal narrative directly reinforces the product's marketing claim that files never leave the browser, so there is a clear interest in the hand-rolled decision reading as prudent. Offsetting factors: the author publishes the code unminified, documents his own bug, and states an open question he could not resolve.
Coherent single-source practitioner account
Confidence is moderate: the technical claims are specific, mutually consistent, and of a kind that practitioners can verify quickly, and the code pointer plus reproducible snippet raise credibility. It is capped by the absence of any corroborating source, the self-interested position of the author, the unresolved cross-browser question, and the lack of any adoption evidence beyond one self-run site.
build
A GAN beauty filter is a device budget allocation, not a feature toggle1 distinct publisher
build
Every viewer hits your HLS key endpoint in the same second, and almost nobody tests it1 distinct publisher
build
The optional EntityManager is the bug: moving the transaction boundary into AsyncLocalStorage1 distinct publisher
build
A Timed-Out Reset SMS Is Not A Failed One, And Your Retry Code Probably Disagrees1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026