Trust and security
Last updated 5 September 2026
This page is for whoever decides whether Kodukeel is safe for a class, a team or a grant. The short answers come first, and each one takes you to the detail below.
- Who answers for itUpthink Solutions OÜ
- Where the data is keptIts own database, with some services outside the EEA
- Trackers and analyticsNone
- Export and deletionAny time, from Settings
- Outside audit or certificateNot yet: no SOC 2, ISO 27001 or pen test
- AccessibilityChecked on every change. Partial, with the gaps named
Who runs this
This installation is run by Upthink Solutions OÜ, registry code 16683946, at Aiandi tn 8/2-28, Mustamäe linnaosa, 12915 Tallinn, Harju maakond, Estonia. They’re the controller of every learner’s data here, and the party any contract would be with.
One address reaches a real person, whether it’s a data question, a security report or a procurement question: privacy@upthink.ee. There’s no separate security mailbox yet.
Where the data is held, and who else touches it
Everything a learner does is held in this installation’s own Postgres database. Nothing below gets a deck, a review history or an exam paper. The list is read from this deployment’s configuration, so it is the set of services this copy talks to.
- Groq: what you type to Anu, and any page you photograph. Established outside the European Economic Area.
- Google Gemini: what you type to Anu, and any page you photograph. Established outside the European Economic Area.
- Anthropic: what you type to Anu, and any page you photograph. Established outside the European Economic Area.
- Ekilex, at the Institute of the Estonian Language: a single word you looked up, with no account attached. Established in the European Economic Area.
- Wikimedia, which runs Wiktionary: the same single word, asked for its English meaning, or an English word you searched for, asked for its Estonian, with no account attached. Established outside the European Economic Area.
- Resend, which sends our emails: your email address, and whatever a message to you says about your own course. Established outside the European Economic Area.
- The push services run by Google, Mozilla, Apple and Microsoft: a reminder you asked to have on a computer, encrypted so that only that browser can read it. Established outside the European Economic Area.
- TartuNLP, at the University of Tartu: a phrase you asked to hear read aloud, with no account attached. Established in the European Economic Area.
- Supabase: your email address and everything in the database, as the host of both. Where this one sits depends on how the installation was set up, so ask the operator above.
- Vercel, which runs the servers this app lives on: every request you make, while it's being answered, and a log of it that includes your IP address. Established outside the European Economic Area.
Some of that leaves the European Economic Area, which matters for a transfer assessment. It rests on the standard contractual clauses each provider publishes. The two features that do it are the tutor and the page scanner, and a deployment configured with no AI provider has neither, so an organization that cannot accept the transfer can run the rest of the app without it.
There’s no analytics vendor, no advertising identifier and no third-party tracker: the app has no third-party script tag anywhere in it, and the one thing it counts, whether people come back, is worked out from its own review log. Privacy is the long version of all of this, written for the learner rather than the buyer.
How a learner gets their data out, and how they delete it
Export. Settings has a button that returns everything held about the account as a single JSON file: every card, review, task, setting, scanned word list, level check, mock exam paper with the composition in it, tutor message, conversation, suggestion, starred word and class membership. The same file restores into a fresh installation.
Deleting everything. The same screen deletes the account and everything in it, including the sign-in record, in one action and with no request to write. Where an installation is not configured to remove the sign-in record itself, the button says so plainly instead of reporting a success it did not achieve. Both live under Settings.
A learner in a class or a workplace group can leave it, which stops the sponsor seeing anything and takes nothing out of their own deck.
What a teacher sees of a class is drawn over an invented one on the page for teachers.
An organization deciding whether to try it with a group can read what a pilot is, including what it is not ready for.
Every claim this project makes about itself is listed on how we know, with its figure, the day it was measured and the command that measures it again.
Which part of the state examination each unit of the course prepares for, and where it has nothing, is on what the course covers.
Security posture
The security work is written down in full rather than summarized for you. There are three documents, and each one names files you can open:
- Security review and threat model: what the system is, the five trust boundaries, fifteen threats worked through one at a time, the controls inventory, and a section on what has not been done.
- Incident response: severity, who does what, the Article 33 clock for telling the supervisory authority, the Article 34 clock for telling the people affected, and runbooks for a leaked credential, a runaway AI bill, a database restore and a vandalized dictionary.
- Control map: the controls a reviewer usually asks about, mapped to where each one lives.
What this project does not have, stated plainly. There is no SOC 2 report. There is no ISO/IEC 27001 certificate. The control map is a self-assessment written by the people who wrote the code, and it has not been reviewed by anybody outside this project. No external penetration test has been commissioned, so nobody has attacked this application under contract, and no independent reviewer has read the source for security faults. The code being public is not the same thing as having been audited.
Four more limits. Ownership of rows is enforced in application code and asserted in the build rather than by Postgres row level security. The Content Security Policy carries ‘unsafe-inline’ in its script sources, for a reason written out in the code, and that is the weakest line in it. Nothing watches the logs continuously: there is no intrusion detection and no alerting beyond an optional error webhook. And multi-factor authentication is inherited from whatever the learner’s Google account has rather than enforced here.
Instead there is a build that fails when a rule is broken: the credential scan greps the built client bundle for every server-only value, the invariant suite asserts the rules this project set itself, and the browser suites drive the real app. All of it runs on every change.
One of those suites asks these questions of a running server rather than of the source. It sends forged requests, reads back every security header, checks that what is behind a token stays behind it, and reads what the health endpoint is willing to say. On its first run it found that a request carrying an address the app could not parse was being treated as a request carrying none. It is written by the people who wrote the code, so it cannot tell you the design is right, and it is not the outside review this section says is missing.
Availability
There is no contractual service level today. No uptime percentage is promised anywhere in this app or in its terms. What follows is what the app does when things break.
The review path survives losing the network. A grade answered with no connection goes into a queue in the browser and is sent later with the time it was actually answered, never dropped and never restamped, so a session on a train costs nothing. The service worker keeps the pages a learner was last on and an offline screen behind them, so the app opens rather than showing a browser error. The dictionary, the tutor and speech all need a connection and say so instead of serving something stale.
When the database is unreachable, pages that need it fail to an error screen that says nothing has been lost, which is true: the review log is only ever appended to. The message itself stays on the server, because a database error can quote a connection string, and what the screen shows is a reference you can quote back at us.
When an AI provider is having a bad minute, the app moves to the next provider configured rather than failing, and where none answers the feature says so. Nothing that teaches Estonian depends on a model: the dictionary, the deck, the scheduler, the exam and every practice round work with no AI provider at all.
Health check. /api/health answers without a session and returns whether the app is up, whether the database answers, and the commit this build came from. It carries no counts and nothing about anybody. A monitor can poll it.
Reporting a vulnerability
Send it to privacy@upthink.ee with “security” in the subject line. The full policy is in SECURITY.md, including the response times we can actually keep: three working days to acknowledge, ten to tell you whether we agree it is a problem, and a target of thirty days to fix anything critical or high. If the report is sensitive enough that plain email worries you, say so in one line with no detail in it and we’ll set up another channel.
If a breach ever affects personal data, the incident document above is the procedure we follow, and the supervisory authority for Estonia is the Estonian Data Protection Inspectorate (Andmekaitse Inspektsioon).
Accessibility
Contrast is measured in a browser in both themes, every target is measured against 44px under a coarse pointer, axe runs over every route on every change, and no audit by a person with a disability using assistive technology has been commissioned yet. The accessibility statement says what is claimed, what is tested, and the gaps that are known.
Privacy, Terms, Funding, Trust, For teachers, Running a pilot, Accessibility, The state examination, Help, Sign in, Contact