Security

Last updated: 2026-10-24. What is protected, what is not, and how to tell us when we got it wrong.

1. How your data is protected

Data source credentials are encrypted at rest. Your Notion token is encrypted with AES-256-GCM before it reaches the database, using a 256-bit key held only in the server environment, never in the database, never in the source tree. Each value gets a fresh random 96-bit IV. The cipher is authenticated, so a tampered ciphertext fails to decrypt rather than decrypting to something wrong. Anyone reading a database dump gets ciphertext and nothing else, with no caveat: the read paths no longer accept a plaintext token at all, so a row that is not encrypted does not work rather than working quietly.

Those tokens are decrypted in memory only for the duration of a single API call. They are never sent to the browser, never written to a log, and excluded from your data export.

Data connector credentials get the same treatment: a database password, an OAuth refresh token or an API key is encrypted with the same AES-256-GCM key before it is stored.

A data connector only reads from your source. A database sync runs inside a read-only transaction that is rolled back when the sync ends. An account connected through OAuth is asked only for read-only access to its data, plus its sign-in identity (the openid and email scopes), whose email address names the connection. A connector that takes an API key refuses a full secret key, asks for a restricted one, and only sends read requests with it.

Everything is served over HTTPS.

Accounts use email and password, or Google or Notion sign-in. A password is stored as a hash, never in plain text; Google and Notion sign-in store no sign-in token. Sessions are token-based, and each session record keeps the IP address and user-agent of the device that signed in, so an unfamiliar sign-in is visible. Resetting a password destroys every session on the account, including the attacker's. Reset links expire in one hour and work exactly once.

An embed URL is itself the credential for a private chart, so it is treated as a secret: embed pages send no referrer at all, not even same-origin, and run under a locked-down Content-Security-Policy that frames them into the app and Notion and nowhere else. The password reset page does the same.

Error monitoring runs with Sentry's "send default PII" switched off, and every outbound event first passes through a scrubber that redacts embed tokens, share tokens and password reset tokens from URLs, error messages, breadcrumbs and span data.

Sign-up, sign-in, password change, password reset and embed requests are rate limited through Upstash Redis.

Security-relevant actions (password changes, password resets, account deletion, plan overrides, admin impersonation) are written to an audit log with the acting user's ID and a timestamp. Deleting an account does not erase those rows; an audit log you can erase is not an audit log.

2. What we deliberately do not store

Notion row content. Row values are fetched on demand and held in a short-lived server cache that expires on its own timer, between 30 and 300 seconds depending on plan. They are never written to our database.

Notion page content. We read a connected database's schema and its rows through the Notion API and nothing else: no page bodies, no images, no comments, no database you did not connect. We make no write calls at all.

Card numbers, CVCs and bank details. Checkout and the billing portal are hosted by our payment processor; that data never touches our servers.

Analytics and tracking inside the app. The app, your dashboards and your embeds load no analytics product, no advertising pixel and no behavioural tracker. Only the public marketing and legal pages load Google Analytics, to count visits, and only after the visitor accepts it on the cookie banner; the Privacy Policy sets out what it receives. The only usage number we keep about your charts is a per-chart view counter.

One honest exception, and it is deliberate rather than accidental: an uploaded CSV is stored in full, because there is no upstream to re-fetch it from. It is capped at 5 MB and 50,000 rows, and deleting the connection or the account deletes it.

The second exception follows from the first: the rows a data connector syncs are stored, because a sync is a copy. Each sync replaces the stored copy with what your source returned, up to 50,000 rows and 10 MB, on the schedule you pick for that dataset, or only when you ask if you set it to manual. A connector that reports monthly revenue also keeps one amount per customer ID for each month, for up to 37 months, so past months stay as they were; it keeps no names or email addresses.

Deleting a dataset deletes its rows. Deleting a connector deletes its credentials (database backups keep an encrypted copy for up to 30 days) and any monthly revenue history it kept, but keeps its datasets, with the rows they last synced, so your charts and embeds keep working; delete those datasets as well if you want the rows gone. Deleting the account deletes every connector and dataset in your organization. For a connector signed in through OAuth, we also ask the provider to revoke the grant, unless another connector still uses that account. A database password or API key is not revoked for you: change or delete it at the source.

3. What we do not have

No SOC 2 report. No ISO 27001 certification. No external penetration test. No 24/7 on-call rotation. No security team.

ChartOmodo holds none of the certifications above yet. The controls in section 1 are real and you can hold us to them; the certifications are not, and we will not imply otherwise to get through a procurement form. If your review requires any of them, this is the page that saves you the meeting.

Detection depends on error monitoring and on our own checks, not on someone watching a dashboard overnight. If something breaks at 3am, it is seen in the morning.

4. Reporting a vulnerability

Send it through the Support page, which lists the address that is actually monitored. Please do not open a public issue or post it publicly before we have had a chance to fix it.

Include enough for us to reproduce it: the steps, the affected URL or endpoint, what you expected and what happened instead, and what an attacker could actually do with it. A proof-of-concept request, a short screen recording, or the exact payload all help. If it depends on a specific account state, say which.

We confirm receipt within 2 business days. Reports go straight to the engineers who build ChartOmodo: no queue in between, and the reply comes from the people who will fix it.

5. Safe harbour

If you research in good faith and stay inside the scope in section 6, we will not pursue legal action against you, will not report you, and will treat your report as an authorised test. If a third party brings a claim about research that followed these rules, we will say publicly that it was authorised.

We credit reporters who want credit: tell us the name or handle to use. If you would rather stay anonymous, that is fine too.

There is no paid bug bounty. No money, no swag, no points. We would rather be straight about that up front than have you find out after the work.

6. Scope and rules

In scope: chartomodo.com, the application behind it, its API and its embed endpoints.

Out of scope: the systems of our sub-processors (our hosting, payment, email, error monitoring and rate limiting providers) each run their own programme, and reports about them belong with them, not with us.

Also out of scope: denial of service, volumetric or load testing of any kind; social engineering or phishing aimed at the operator, at any customer, or at a sub-processor's support staff; physical attacks; spam; and raw automated scanner output with no demonstrated impact. Missing best-practice headers, weak cipher suggestions and version disclosure are not vulnerabilities unless you can show what they let you do.

Test against your own account and your own data only. The moment a test would touch another customer's account, data, chart or embed, stop and report what you have. Do not modify or delete data that is not yours, and do not exfiltrate more than the minimum needed to prove the finding: one record is proof, a thousand is an incident.

Keep automated traffic to a rate a normal user could produce, and do not attempt to degrade the service for anyone else.

7. Disclosure

Please give us a reasonable window before publishing. We ask for 90 days from the day you report, which is the industry norm, and we will tell you as soon as it is fixed, usually well before that. If a fix is genuinely going to take longer, we will say so and explain why rather than go quiet.

Once it is fixed you are free to publish. We are happy to review a draft for accuracy if you want a second pair of eyes, and we will not ask you to change anything except a factual error.

8. If ChartOmodo shuts down

If ChartOmodo is ever discontinued, we will give at least 90 days' notice by email and on this site. During that period your embeds keep working and you can export every chart configuration and your uploaded CSV rows from Settings → Account → Export your data.

That export is one JSON file. It carries every chart's configuration in full; uploaded CSV rows are included up to 20,000 rows across 10 files, and the file itself says so when it had to leave rows out. Your Notion data was never stored here, so there is nothing of it to export: it stays in Notion.

9. Contact

Vulnerabilities, account questions, billing, privacy and security questionnaires all reach the same team. The Support page is the single route in, and it is the route published at /.well-known/security.txt. Receipt is confirmed within 2 business days.

There is no separate security alias: one inbox that is read beats a second address that is not.

May we use Google Analytics cookies to count visits to our public pages, and remember which link brought you here in case you sign up? Nothing inside the app or your embeds is ever tracked. Privacy Policy