Security
Verify what leaves your browser with Tabulys
Tabulys reads and compares your files in your browser. This page gives you what you need to check that no request carries their content: the Content-Security-Policy the server sends, what each of its directives allows and refuses, the requests the site sends, and two procedures to see it for yourself.
The Content-Security-Policy
The server sends this Content-Security-Policy header with every response, pages, scripts and WebAssembly modules included. The text below is read from the server configuration each time this page is served.
default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
default-src 'self'- Any kind of resource that no other directive names may load from this site only. It is the fallback for the directives this policy leaves out, such as media-src, manifest-src or frame-src.
script-src 'self' 'wasm-unsafe-eval'- Scripts run only from files served by this site: no inline script, no script from another origin, and no eval() or new Function(). 'wasm-unsafe-eval' lets the site compile WebAssembly, which the comparison engine (DuckDB-WASM) needs; it does not allow eval() of JavaScript.
style-src 'self'- Style sheets load only from this site; inline style elements and style attributes are refused.
img-src 'self' data:- Images load only from this site, or from data: URLs, whose content is carried inside the page or its style sheets and fetched from no server.
font-src 'self'- Fonts load only from this site.
connect-src 'self'- Scripts, workers included, can send requests (fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon) to this site only. It does not limit what is sent to this site, which is why the requests are listed below, and it does not cover a navigation to another page nor a WebRTC connection, which no directive of this policy restricts.
worker-src 'self'- Web workers and service workers start only from scripts served by this site. The comparison runs in such workers; the site registers no service worker.
object-src 'none'- No plugin content (object or embed elements) loads.
base-uri 'self'- A page cannot set its base URL to another origin, which would point its relative links and scripts elsewhere.
form-action 'self'- Forms can be submitted to this site only.
frame-ancestors 'none'- No site, this one included, can show these pages in a frame: another site cannot lay them under its own buttons (clickjacking).
The requests the site sends
All of them go to this site. A path ending with a slash covers every file under it. Beside this list, submitting the log-in or log-out form sends POST /login or POST /logout.
GET /: the HTML of the home page.GET /compare: the HTML of the comparison page.GET /compare-excel-files: the HTML of the “compare Excel files” page, the comparison page with its own title.GET /compare-csv-files: the HTML of the “compare CSV files” page, the comparison page with its own title.GET /excel-diff: the HTML of the “Excel diff” page, the comparison page with its own title.GET /spreadsheet-compare: the HTML of the “spreadsheet compare” page, the comparison page with its own title.GET /compare-two-lists-in-excel: the HTML of the “compare two lists in Excel” page, the comparison page with its own title.GET /security: the HTML of this page.GET /pricing: the HTML of the pricing page.GET /legal-notice: the HTML of the legal notice.GET /terms: the HTML of the terms of use.GET /privacy: the HTML of the privacy policy.GET /login: the HTML of the log-in page.GET /waitlist/confirmed: the HTML of the page that confirms a place on the waiting list, opened from the confirmation email.GET /build/assets/: the scripts and style sheets of the pages, thesiteentry on every page, thetoolentry on the home page, the comparison page and the five pages “compare Excel files”, “compare CSV files”, “Excel diff”, “spreadsheet compare” and “compare two lists in Excel”, and thewaitlistentry on the pricing page, and the fonts of the site. Their names carry a hash of their content.GET /favicon.ico: an icon of the site.GET /favicon.svg: an icon of the site.GET /apple-touch-icon.png: the icon of the site on Apple devices.GET /apple-touch-icon-precomposed.png: the icon older Apple devices ask for on their own.GET /build/assets/: the files of the comparison engine, loaded when you drop your first file and never before: the comparison worker, DuckDB-WASM, its worker and its.wasmmodules, SheetJS for Excel files and Papa Parse for CSV files. This site serves them; no third party does.-
POST /telemetry:audience measurement, one JSON event per request. The server refuses any event that does not match these definitions exactly. The opt-out button at the bottom of every page stops all of them; offline, an event is lost, with no queue and no retry.
-
comparison_done: Sent once per comparison that reaches a result.-
event_name: The name of the event. Alwayscomparison_done. -
browser_id: A random identifier drawn by this browser at its first comparison and kept in its local storage. Matches^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$. -
schema_hash_a: An HMAC-SHA-256 of the normalized column names of file A, keyed by a random key that is drawn in this browser and never sent. Matches^[0-9a-f]{64}$. -
schema_hash_b: An HMAC-SHA-256 of the normalized column names of file B, keyed by the same key. Matches^[0-9a-f]{64}$. -
entry_page(optional): The path of the first page of the site this browser visited, without its query string. Matches^/[a-z0-9/-]{0,120}$. -
referrer_category(optional): The kind of site the first visit came from, read from the host of the referrer only. One ofsearch,ai_assistant,other,direct. -
browser_family: The family of the browser, read from its user agent. One ofchrome,edge,firefox,safari,other. -
row_band: The size band of the larger of the two files, in data rows. One ofrows_1_to_1000,rows_1001_to_10000,rows_10001_to_100000,rows_100001_to_1000000,rows_over_1000000. -
duration_in_milliseconds: How long the comparison took, in milliseconds. An integer from 0 to 86400000. -
engine_version: The version number of the comparison engine. Matches^[0-9]{1,6}$.
-
-
export_done: Sent once per export of a result.-
event_name: The name of the event. Alwaysexport_done. -
browser_id: A random identifier drawn by this browser at its first comparison and kept in its local storage. Matches^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$. -
export_format: The format of the exported file. One ofxlsx,csv.
-
-
save_clicked: Sent once per click on "Save for next month".-
event_name: The name of the event. Alwayssave_clicked. -
browser_id: A random identifier drawn by this browser at its first comparison and kept in its local storage. Matches^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$. -
schema_hash_a: The schema hash of file A of the result shown, as sent with comparison_done. Matches^[0-9a-f]{64}$. -
schema_hash_b: The schema hash of file B of the result shown, as sent with comparison_done. Matches^[0-9a-f]{64}$. -
entry_page(optional): The path of the first page of the site this browser visited, without its query string. Matches^/[a-z0-9/-]{0,120}$. -
browser_family: The family of the browser, read from its user agent. One ofchrome,edge,firefox,safari,other.
-
-
waitlist_joined: Sent once per accepted sign-up to the waiting list, without any identifier and never joined to the address.-
event_name: The name of the event. Alwayswaitlist_joined.
-
-
import_failed: Sent when a file cannot be read or a comparison cannot complete, without any identifier.-
event_name: The name of the event. Alwaysimport_failed. -
file_type: The format of the file concerned, as read or as told by the extension of its name; unrecognized when neither tells it. One ofxlsx,csv,unrecognized. -
failure_cause: The cause of the failure, from a closed list. One ofunrecognized_format,unsupported_spreadsheet_format,legacy_xls_unsupported,utf16_unsupported,password_protected,corrupted_file,empty_file,invalid_utf8,malformed_csv,empty_header_row,capability_missing,worker_error,engine_unavailable,memory_suspected,partition_invariant_broken,export_cell_too_long,interface_error.
-
-
-
POST /waitlist:a sign-up to the waiting list, sent only when you submit its form. Its JSON body holds these fields and no other:
consent: Always true: the box you ticked to be emailed when the monthly replay opens.email: The address you typed. The server relays it to Brevo, the email service that sends the confirmation message, and does not store it.website: Always empty: a field hidden from people, which only bots fill in.
Check the requests in the developer tools
- Open the comparison page, open the developer tools of your browser (F12, or Cmd+Option+I on macOS) and select the Network tab. Reload the page so the list starts with it.
- Drop two files, answer the questions until the result shows, then export it.
- Read every request in the list: each goes to this site, and its method and path are in the list above. Requests sent by the workers of the comparison show in the same list.
- Open each
POST /telemetryrequest and read its payload: only the fields defined above, with no cell value, no column name and no file name. - Select the request of the page itself and read its response headers: the
Content-Security-Policyheader is the policy published above, character for character.
Compare with the network cut
- Open the comparison page, run a first comparison to its result and export it. This loads the files of the comparison engine.
- Cut the network: turn off Wi-Fi or unplug the cable, or choose Offline in the Network tab of the developer tools.
- Without reloading the page, drop a new pair of files.
- The comparison completes and the export downloads.