Legal
Security
How one company's content is kept away from another's, what is encrypted, what we can see, and what we do not have.
Last updated 28 September 2026.
How one company is kept out of another
Not by remembering to add a WHERE clause. Every table that holds customer data has a row-level security policy in Postgres, and the application talks to the database as a role those policies apply to.
Every request opens a transaction, sets the company, the person and their role as settings on that transaction, and then switches to the restricted role. From that point a query can only see rows that belong to that company and that the person's role covers. A query that forgets its filter returns nothing rather than too much, which is the point: the failure mode is an empty screen, not a leak.
This is tested, not assumed. A suite runs against a real Postgres on every change and checks that a member of one company reads nothing from another, that a client on a project cannot reach team or leadership content, that an admin with no project membership reads no project content, and that the same holds for items, passages, memory and board cards.
Background jobs and webhooks go through the same layer with a system flag, scoped to one company at a time. There is one database connection that bypasses row-level security, because it owns the tables. Only a short list of files may use it — the tenancy layer, sign-in, the assistant connections, the live event bus, the health check, account and company deletion, the personal data download, and our own administration area — and a lint rule refuses to compile anything else that imports it.
Who sees what inside a company
Every piece of content carries an audience: client, team or leadership. A client on a project sees only client content. A lead, member or observer sees client and team content. Leadership content is added only for people who have been given leadership access, and only someone who already has it, or a company owner or admin, can give it.
Anything the app derives takes the narrowest audience of what it was built from. A memory entry quoting a leadership meeting is leadership. A brief compiled for clients is compiled from client content only. When a source is narrowed or content is deleted, the briefs built from it are discarded and a compile that had already read the old input is not allowed to save its result.
Chats are private to the person who wrote them, enforced by the same policies. The notification bell stores ids and never sentences, so it reads the words back through your access at the moment you open it — which means a notification about something you may no longer see shows you nothing.
What is encrypted, and with what
Precisely this, and nothing is claimed beyond it:
- Your API keys and webhook secrets — the Anthropic, Soniox and TypeSafe keys a company enters, and the signing secrets of its incoming webhooks, are encrypted with XChaCha20-Poly1305 before they are stored, under a key held only in the server's environment and never in the database. The app shows you the last four characters of a key and nothing more, and never sends a key to the browser.
- Connector tokens — the Slack and Jira tokens from your company's own install, and the token of a GitHub account someone connects for themselves, are encrypted the same way, in the same place. For the company's GitHub install no token is stored: each request mints a short-lived installation token from the app's own private key.
- Passwords — hashed with scrypt, which costs 32 MB of memory for every guess. We cannot read them, and signing in with a one-time link or with Google means you need not have one at all.
- Out of reach of the app itself — sessions, password hashes, two-step secrets and backup codes, personal-key hashes, sign-in links, the tokens Google returns at sign-in and the assistant connections' OAuth tokens live in tables the application's database role has no permission on whatsoever. A bug or an injection in application code cannot read them, because the role it runs as cannot.
- In transit — HTTPS everywhere, with certificates from Let's Encrypt, terminated at the proxy in front of the app. The connection to the database never leaves the host's private network.
- The database itself — not encrypted at rest beyond whatever the disk provides. See "What we do not have".
Signing in
You sign in with a one-time e-mail link, a password, or Google. Google is asked only for your name, e-mail address and picture, and an address Google has not confirmed is refused.
Anyone can turn on two-step sign-in in Settings: a code from an authenticator app, with ten backup codes for when the phone is not at hand. There are no codes by SMS or e-mail, because a stolen mailbox would then be both steps. Once it is on, it is asked for after every way in — a password, a link or Google — unless you ticked "Trust this device" there in the last 30 days.
Changing your password signs you out everywhere. Changing your e-mail address takes two links: one approved from the old address and one confirmed from the new. Both are recorded in the activity log of every company you belong to.
Sign-ups can be closed so that only invited people get in. Inside a company, someone who is not an owner or admin can invite people only into projects they lead.
What we can and cannot see
We can read the database. Anyone who can operate the server can, because the encryption key for stored secrets sits in the same environment as the application that needs it. That is true of almost every product of this shape, and a security page that implies otherwise is not describing a real system.
What happens in practice is a promise rather than a mechanism, and it should be read as one: the people who run the service do not look at project content. They look when they are investigating a fault, or when a customer has asked for help with a specific problem. There is no internal tool for browsing it.
The part that is enforced rather than promised matters more, and it is the part you can check. Ordinary use of the product cannot read across a company or past an audience: row-level security decides what every query returns, and the application's database role has no privilege at all on the session, account and key tables. That is the database refusing, not somebody remembering.
What nobody can read, ourselves included: your password, and a personal key once it has been issued. Both are stored only as hashes. Access to the production server is limited to the people at Dodera Software who run it.
Our own administration area is open only to the addresses we list in the server's settings, and only with two-step sign-in and a confirmed address; to anyone else it does not exist. It shows companies and people, never content, keys or tokens. Every change made there needs a fresh two-step code and is written to a log of its own that the application cannot alter.
The audit log
Every change that matters is recorded: adding or removing an AI key, adding, removing or re-roling a person, creating or deleting a project, connecting or removing a source, changing who can see something, changing retention, a change of plan, exporting data, and what an assistant did through the connector. Company owners and admins read it in Settings, filtered by person, action and date.
It deliberately records what changed and not what was in it. The entry for a key says that a key was set, never the key. The entry for a source says which source, never a message from it. Project content never goes into an audit entry. Where the change is a person — an invitation sent, a role given — the name or e-mail address is recorded, because that is the change.
The log is never swept by the retention job; it is kept for as long as the company exists. It is read in the app, filtered and paged, and owners and admins can download it with the rest of the company’s data.
Where it runs
The server, the database and the uploaded files are in Nuremberg, at Hetzner Online GmbH. Hetzner is a German company running German data centres, so that infrastructure is both located in the EU and owned by an EU company, and is not subject to the US CLOUD Act the way an EU region of a US-owned cloud is. Our e-mail goes through Hostinger, in the EU, and product analytics are stored in PostHog's EU region.
What leaves the EU is what your own AI providers receive, under your own account: Anthropic in the United States; Soniox, if you record with it; TypeSafe, if you turn it on; and your browser's own speech service when that engine is used, which on Chrome and Edge means Google. Those are your contracts, not ours. Payments go through Stripe and never touch our server.
Both halves of that are true, and the line between them is exactly where it looks. What we hold, we hold in Europe. What you send to a model, you send yourself.
Server errors are reported to our own Slack channel with the error's name, a cleaned message and the page address with every id masked, never content.
Recordings
No audio file is ever written down by this application — not to disk, not to the database, not to a temporary file of ours. What is saved is text.
With the default engine, the audio never reaches us at all; the browser transcribes it. With Soniox, it goes from the browser straight to Soniox on a single-use key that expires in five minutes, and our server never sees it.
The microphone and screen capture are only available on our own origin, and the camera and location are switched off by a policy the browser enforces.
Assistants and personal keys
Someone can connect their own assistant to their projects at https://intelilang.com/mcp, either by signing in through our own OAuth server, which asks which company the connection is for, or with a personal key they generate in Settings.
Whatever the assistant asks for goes through the same tenancy layer and the same policies as the web app, so it reads exactly what that one person can read, in that one company, and nothing else. A personal key is bound to one person and one company, stored as a SHA-256 hash, shown once, limited to 120 requests a minute, and revocable from Settings. It does not expire on its own, so revoke one you have stopped using. An assistant signed in through OAuth is checked against the consent on every single call, so disconnecting it in Settings cuts it off at once rather than when its token runs out.
Remember what this means for the content: what an assistant reads goes to whoever runs that assistant.
The application itself
Sessions live in an http-only, secure, same-site cookie that expires after seven days, and signing out deletes the session on the server. There is no session copy in the cookie: every request checks the session against the database. Sign-in links work once and last fifteen minutes.
Requests are rate limited to 600 per five minutes per client address, and the administration area to 120. Sign-in and sign-up allow three attempts in ten seconds, a sign-in link five in a minute, a password reset three in a minute, and a personal key 120 in a minute. A request that streams a body without saying how big it is is refused; uploads stop at 50 MB, recordings at 60 MB and webhook deliveries at 256 KB, and every webhook delivery must carry a valid signature less than five minutes old. Every input crossing the boundary is checked against a schema.
Because the session cookie is same-site, another site cannot make your browser act as you. Sign-in also checks the origin of the request, and the live update socket accepts an upgrade only from an origin that sign-in already trusts — which is what stops another site opening one with your cookies.
The app is served with HSTS for 180 days, framing limited to our own origin, MIME sniffing off, no referrer sent, and a nonce-based content security policy — the standard profile rather than the strictest one, so inline styles are still allowed. It runs as an unprivileged user in its container, and migrations finish before it serves a single request.
No tag manager, no advertising, no error-reporting service, no CDN and no web fonts: the page you get is the page we built. The one thing loaded from someone else is product analytics, from PostHog's EU servers, within the limits on the Privacy page: nothing for a signed-out visitor until they choose Allow, screen recordings only for people who allowed them, off for anyone who turns it off in Settings and for a whole company whose owner or admin turns it off, never any content, and every field hidden.
If something goes wrong
Some of this is not ours to promise. When personal data held here is breached, the GDPR sets the clock: Article 33 gives us 72 hours from becoming aware of it to report it to the supervisory authority, unless it is unlikely to put anyone at risk.
Where a breach is likely to be a high risk to the people affected, Article 34 says they have to be told as well, without undue delay.
For content your company put in, your company is the controller and the report to the authority is yours to make. We tell you without undue delay, with what we know, so that you can make it in time.
What we do not have
A page that lists only strengths is not worth reading. These are the things InteliLang does not have today, and why.
- No backups we can promise — we do not yet run automated, tested backups of the database and the uploaded files, so we do not promise that lost data can be restored. The originals of connected content stay in Slack, GitHub and Jira, and owners and admins can download everything the company holds at any time. We will say so here when that changes.
- No SOC 2 — a SOC 2 report costs tens of thousands of euros a year and proves that we follow the controls we wrote down. It does not make the product safer, and at our size the money buys more safety spent elsewhere. We will get one when a customer needs it to buy, and we will say so here rather than implying we already have it.
- No ISO 27001 — the same reasoning. Nobody has asked yet.
- No external penetration test — this one we do intend to fix. The tenancy layer is covered by tests that run against a real database on every change, which is the part we most want an outsider to try to break.
- The database is not encrypted at rest — your API keys and connector tokens are encrypted inside it, but the content is not. Full-disk encryption on a server that must boot unattended keeps the key on the same machine, so it protects against a stolen disk and against very little else. We would rather say this plainly than let the phrase "encrypted at rest" do work it cannot do.
- No SSO or SAML — you sign in with a link, a password or Google, with two-step sign-in on top if you turn it on. A company cannot yet make two-step sign-in compulsory for its people. Single sign-on is the thing most likely to be added next if companies ask for it.
- No e-mail verification for password sign-ups — signing in with a one-time link proves the address by definition; setting a password at sign-up does not, and we do not yet make you confirm it. Sign-ups can be closed so that only invited people get in, which is the control that matters more.
- No high availability — one server, one database, background jobs in the same process. A deployment or a failure is downtime. See the Terms: there is no uptime commitment.
- No bug bounty — we read every report and answer it, but we do not pay for them.
Reporting something
If you have found a vulnerability, write to us with enough detail to reproduce it. We reply within five working days and tell you what we intend to do about it. Please give us time to fix it before you publish. There is no bug bounty, and we will credit you if you want to be credited.
office@doderasoft.com