Sonela Security review · print to PDF

Security and read-only validation

This page is for whoever has to approve letting Sonela read your business data: every guarantee below is a property of the code — a grammar, a transaction mode, a column that does not exist. Sonela is an enterprise knowledge AI platform powered by retrieval-augmented generation (RAG): the model answers only from what Sonela retrieved, under the rules below.

Where Sonela runs

One rung is the page your users are already on. The other two are the choice you make about your database, and they carry different trust boundaries — both are described here rather than only the flattering one.

In the page

One script tag and a widget key — the whole change to your application. The page holds nothing secret: the widget key is public by design, and the tenant identity is bound server-side from a signed token the browser cannot mint. Editing the page's JavaScript reaches no other tenant's rows, and no key of yours. An Origin header is a courtesy, never identity: sessions held to signed-in users open only on a signed voucher outsiders cannot forge — ours do, and yours can. The whole mechanism is set out under tenant identity verification below. The person on the page sees the guarantees too: a shield in the assistant's header opens them in plain words, and when page capture is on, the person being read is told.

In your network — the Sonela Gateway

One small service, on one server of yours, next to the data it reads — never on a staff machine. It dials out to us over port 443, so you open no inbound port and expose nothing to the internet. What it needs to read your data is configured on your side and stays there — run this way there is nothing for us to store, and a row holding null cannot leak. We keep a hash of the gateway key, and both validation and execution run on your side of the firewall.

On our infrastructure — a direct connection

The setup with nothing to install, and it costs you a trust decision the gateway does not. You give us what your database expects at sign-in, and our servers open the connection themselves. Those details sit on our infrastructure. We hold them encrypted with AES-256-GCM, bound to the one workspace and data source they belong to, and no API we expose reads them back — but encryption is what we do about that exposure, not a reason there isn't one, and the exposure is the thing to weigh. We refuse a target that points at our own database, and we test the connection before accepting it.

  • The three layers below all still apply — on this path they run on our infrastructure rather than yours.
  • The hop to your database is secured by what you configure on it; we do not impose that setting, so set it if your review needs it.
  • There is no fixed outbound address to put in your firewall today. We do not offer one and will not name one. If your review needs an allowlisted source, choose the gateway.

Read-only, enforced three times

Three independent layers: all of them have to fail before the assistant can change anything, and each is stated per engine below rather than borrowed across engines. Behind the gateway they run inside your network; on a direct connection they run on ours. Which machine they run on changes; that they run does not.

1

An allowlist grammar, before the query runs

Model-written SQL is refused unless every part of it is explicitly permitted — syntax nobody anticipated is denied by default.

  • Must begin with SELECT. Any semicolon is refused, so the input stays one statement.
  • Every keyword and function must be on the allowlist, per engine, and each engine's system catalogue is unreachable — pg_*, information_schema and dblink on PostgreSQL; xp_*/sp_* procedures, OPENROWSET, hints, WAITFOR and the sys* views on SQL Server, whose lexer refuses @ and [ outright; LOAD_FILE, INTO OUTFILE and the query-modifier keywords on MySQL, whose lexer refuses @, backticks and # comments outright; packages such as DBMS_* and UTL_*, sequences and the V$ views on Oracle, where a name that is no column of a table the query reads is refused too, because Oracle would run a function of that name.
  • Schema-qualified names, comma-separated FROM lists and parenthesised join groups are refused — each one evades the rewrite.
2

The engine's own guard, while it runs

Every question runs under a statement timeout of three seconds and a cap of 500 rows, so the timeout bounds what any one question can cost you. On PostgreSQL and MySQL it also runs inside a READ ONLY transaction: the database session itself refuses a write, whatever reached it. SQL Server offers no such session mode, so there the guarantee is the two layers beside this one — the only statement of the model's that can reach the server is a single validated SELECT, and every session ends in a rollback. Oracle runs it inside a READ ONLY transaction too, which refuses a write the statement itself makes; it does not bind a function that commits on its own, so the rung before this one refuses every call but an allowlisted built-in, and every name that is not a column the query reads.

3

A discard, not a commit

The transaction is thrown away rather than committed: the success path rolls it back and every failure path unwinds it, and there is no commit in the query code for either to reach. A write that somehow executed would not survive the request.

Tenant isolation by rewrite, not by prompt

Every table reference is replaced with a subquery projecting only the columns you approved and filtering on the tenant column you declared. Isolation never depends on the model behaving, and a table absent from your manifest is rejected. The tenant value is a bound parameter, server-bound from a signed session: the browser cannot assert it, the model never sees it. If the database holds one organisation's data and you told us so, there is no tenant column and no such filter — you are asked which it is, and nothing is read from your schema until you answer.

What the model wrote

SELECT "Status", count(*) FROM "Orders" GROUP BY "Status"

What actually ran (PostgreSQL shown; on SQL Server and MySQL an integer flag compares with = 0)

SELECT "Status", count(*) FROM (SELECT "Id", "PlacedOn", "Status", "Total" FROM "Orders" WHERE "TenantId" = @__ai_tenant AND "IsDeleted" = false) AS "Orders" GROUP BY "Status"

Only approved columns are projected, and a table holding a column named like a secret is withheld whole. The statement is then held to the row limit.

Tenant identity verification — signed embeds

A widget key is public by design, so it can open a session but never prove who is asking. Proof is a voucher your own server signs with a secret the page never holds, and you climb to it in three states: with no secret the widget is open to the page it is on; generating the secret arms the workspace and changes nothing a visitor sees; enforcement is a separate switch you flip yourself, after which an unsigned page is refused a new session. Turning enforcement on with no secret is refused too — there would be nothing to verify against.

Two shapes your server can sign

Both are HMAC-SHA256 under your workspace's own signing secret. There is deliberately no issuer check: the secret is the trust root, and a signature that verifies is itself the workspace binding.

  • A JWT. HMAC-SHA256 is the only algorithm accepted, the audience is pinned, and an expiry is required: a token whose expiry sits more than eight hours out is refused whole, and clocks are allowed thirty seconds of skew.
  • A raw triple — user id, timestamp, signature — for a backend without a JWT library. The signature is HMAC-SHA256 over the timestamp and the user id joined by a colon; the timestamp is bare unix seconds, digits only; the signature is hex that must decode to exactly 32 bytes. Freshness is eight hours behind and thirty seconds ahead, and the comparison is fixed-time.

Verification always runs

A signature that arrives is verified whether or not the workspace demands one, and a failure is refused — a bad voucher is evidence of spoofing, never noise: it is written to your workspace's security feed, and five in 24 hours raise a probing warning in your dashboard. A valid one earns the vouched session even before you enforce; only the total absence of a signature consults your enforcement switch.

  • A partial triple — one or two of its three fields — is refused outright.
  • If both shapes arrive, both must verify.
  • A voucher sent to a workspace that has no signing secret is refused, not waved through.
  • The refusal never says which check failed.

The secret, and rotating it

sk_ plus 256 bits from a cryptographic generator, shown exactly once, at generation; at rest we hold it encrypted with AES-256-GCM, bound to its one workspace. Rotating keeps the old secret verifying for 24 hours, so your servers can switch to the new one without an outage. If the old one leaked, end that window early from your dashboard.

What a voucher proves — and what it does not

It proves who your application's server says is asking. It is identity attestation, not transport secrecy: the voucher travels in the page, any script in that page can read it, and there is no replay cache — so until it expires it can be replayed by whoever captured it. The window is the expiry your signer chose, up to the eight-hour ceiling, and it is the whole blast radius, so sign short.

  • An allowed-origins list is a browser courtesy: it stops other websites, not other programs. The voucher is the one check a script cannot forge, because the secret never reaches the page.
  • Until you enforce, an unvouched session still answers — rate-limited to a per-visitor-address slice (IPv6 grouped by /64 subnet) of the workspace's own request rate, so anonymous traffic divides your capacity and never widens it. A vouched session is served at the workspace's full rate, with no per-address slice.

What the assistant sees of the page someone is on

A picture of the screen, taken only while someone keeps the camera pressed, and the data the page already fetched for the person looking at it. Both travel only with a question that person sends, on the signed-in assistant and never the public one.

A screenshot, only while someone keeps the camera pressed

One press turns the camera on, and each question then carries a picture of what the window shows at that moment, at the screen's own sharpness; the whole page only when chosen for one question. A second press turns it off. Nothing automatic, scheduled or on open, and our own widget is cut out of the picture.

  • Beside it goes the text drawn on that screen — headings, labels, table rows, the selected tab — never what was typed into a field, and masked as the question is where masking is on. The picture passes as taken.
  • Each picture rides its own question and never joins the history; its text returns with later questions only sealed in the person's own page, for four hours at most. Never logged, written to our disks or cached, and our metering has no field either could ride in.
  • Checked before use: PNG, JPEG or WebP, two megabytes at most before and after decoding; the text is bounded, and refused without its picture.

What the page's own data reader does — and where it stops

The bundle holds, in memory for the life of the page and never in browser storage, what your application already delivered to its user's browser: at most eight responses, half a megabyte. It leaves the browser only with that person's question, to answer it. A count over it is "of the records on this page", and a list with no total of its own is never read as the whole system.

  • What it can see, both ways. Through the page, every field that person’s browser received, including fields nobody reviewed, with no approval step and only the stripping below. Through the Gateway or a direct connection, only the tables and columns you approved, confined to the asking organisation.
  • Before anything is held, obvious secrets are stripped: values under names like password, token, secret, api_key, card or ssn, and values shaped like a signed token or a random key; depth, string and array length are capped, and only query parameter names are kept. Masked as a lookup's values where masking is on; your switch off holds on our side too.
  • That stripping is a heuristic and we call it one. It matches names and shapes, so it will not recognise personal data sitting under an ordinary business name — a notes field, a delivery address, a date of birth. It is deliberately biased towards removing too much. It is not a guarantee, and a review should not treat it as one.

Two switches over what the assistant sees, and what off actually does

Both start on, because the assistant is more useful with them; both are yours to turn off, for a whole workspace rather than one embed.

Switch Default What turning it off does
Reading the page's own data On On a production embed the reader is never installed, so nothing is captured at all. The setting is held by us and read from us, so a page cannot quietly restore it while the assistant runs normally.
Page screenshots On Refused at the API, whatever the page does: a question carrying an image to a workspace with the camera off is answered with 400 and the picture reaches no model. On the page itself the composer offers no camera once the setting arrives, and the code that takes a picture is never loaded. One limit worth a reviewer's time: a load whose setting never arrived still shows the button, so a visitor there can attach a picture and be refused rather than see it greyed out.

Personal data masked before the model reads it

Before the AI model that writes an answer — yours under your key, or ours — reads anything, Sonela swaps the personal values it recognises for numbered placeholders such as [PERSON_1]: in the question and the conversation so far, the rows a query returns, your own systems' replies and, on every assistant but a public one, your manuals and documents. The model answers with them, and Sonela puts the real values back before the person asking reads the answer.

Always masked, for every workspace: card numbers, IBANs, passwords, keys and tokens, kept out of an answer's words, which say one was removed; and ID, tax, passport and bank account numbers. Your switches: names, contact details (an email before the @, phones), addresses and company names, and whether people asking see real values. New workspaces mask names and contact details; those made before 6 October 2026 leave names in clear until switched on.

To look a record up, Sonela puts the real value in the lookup, or in a query only where a condition compares it with an approved column. The pairings live in memory for one answer; a follow-up is masked again from a sealed note the conversation carries for a day, which only Sonela can open. Masking keeps counts, never values.

Where it stops

It reads format, checksum, column, title or label: a heuristic, not a guarantee. A name or ID number in prose with none of those reaches the model unchanged, as does a screenshot; a recording reaches our speech-to-text provider as recorded. Your business context, schema notes and templates go as written, as do a public assistant's pages, and so does a value typed that one of them already shows; your key's embedding provider, yours or ours, indexes your manuals as written. With real values shown, tables are your rows as returned. Sonela can pause masking for everyone in an emergency, and our hosting platform's monitoring can record a lookup's address, value included.

What Sonela can see — and what it cannot

Sonela is a data processor: your content transits our service to an AI provider — yours under your key and DPA, or one of ours under our agreement and model choice.

Data What actually happens
The question, the rows that answer it, and the page it is asked on In transit Reach Sonela and one AI provider — yours under your DPA, or ours — with the personal values masking recognises as placeholders, and a screenshot, if one is sent, as it was taken. Held in memory for one request, never logged, written to our disks or cached; only the screen's text returns with later questions, sealed in the person's page.
Stored questions, answers or SQL Never A metering row has counts, latency, tokens, provider and model — and no column that can hold a question, an answer or a query. It records which of your tenants asked, for billing. A question's words reach our storage in three cases only: a test question you save yourself to check your assistant, kept in your workspace with your written material, and the next two rows.
An answer someone reports Stored Someone who reports an answer with the thumbs-down beside it is offered a ticked box that sends that one question and that one answer to you, so you can put it right; untick it and the report arrives with no words at all. Only your workspace reads them — we count reports and never see the text. Deleted 100 days after the report, and you can switch reporting off.
A conversation handed to a person Stored Where you switch it on, a visitor who asks for a person can carry on in the same chat with your people, and is told what is kept before they choose to. The conversation so far, each answer in it checked to be one the assistant gave, and what either side writes after it are kept for your workspace alone — we never see the text — and deleted 30 days after the conversation ends. Your people are emailed that someone is waiting, with none of its words.
Written material you upload Stored Procedures and notes you give the assistant are kept in your workspace and sent to that same provider with the question. Workspace-scoped; deleting them removes them. Google Docs, Word and Excel files you sync from Google Drive are kept the same way, read with a service-account key that is encrypted at rest and never returned; a file you delete or stop sharing in Google Drive is removed at the next hourly sync that reads everything you shared, and any file Google has not confirmed for seven days is removed even if no sync could reach it. Word and Excel files you sync from SharePoint or OneDrive for work or school are kept the same way, read through your own Microsoft app with a certificate whose private key is encrypted at rest and never returned; a file you delete in SharePoint, or that your app may no longer read, is removed at the next hourly sync that reads everything you added, and any file SharePoint has not confirmed for seven days is removed even if no sync could reach it. Posts from public Facebook pages and Instagram profiles you add are kept the same way, the latest ten from each: a scraping provider reads the page for us and is given its address and nothing else of yours. A post you delete from the page is removed within a week, at the page's next full reading, and a page no full reading has confirmed for fourteen days loses its posts. Products from a product feed you add are kept the same way: each product’s name, description, price, availability, link, picture address, brand, product codes, categories and specifications, replaced whole at each reading and deleted with the feed or the workspace. Pictures load from your shop’s own address and are never copied, and the address of the page a visitor has open is used for that one question to find its product and to tell the assistant which page it is asked from, by its path and title and never its query string, then dropped.
Transcripts Never In the assistant's panel, the transcript lives in the visitor's browser. We keep no conversation unless a visitor hands it to your people; a reported answer is one exchange out of one, and nothing either side of it. A visitor may email themselves the conversation from the panel; it leaves through our mail provider once, carrying only lines the assistant actually said, and we keep no copy.
Remembered answers Stored An answer may be kept for up to seven days and shown again for the same question, keyed by a hash of the question, never its words. Any change to your manuals, context or rules retires it, and you can switch it off or empty it from the dashboard. An answer grounded in query results or a lookup is never cached.
How your data is reached Your side The gateway you run reaches it from inside your network, with a value configured and held there. Or ours On a direct connection we hold your database sign-in details, encrypted, and reach it ourselves.
Your AI provider key Encrypted Yours: AES-256-GCM, row-bound, never returned. Never On our key: none of yours held.
Write access to your data Never Three layers refuse it, and the last of them discards the transaction rather than committing it.
Your data used to train a model Never We train nothing, and your rows reach the provider that answers and no further.

What is written down instead: two append-only feeds in your dashboard that nothing can edit or delete. One records every refusal at your workspace's doors — a forged signature, a blocked origin, a query outside the allowlist — as a kind and a time, never the text; the other, every switch an admin flips, by whom and when.

Ask us for the threat model

We will send the real one: what we assume, what we deliberately do not defend against, and where the boundaries sit. We hold no security certifications today and would rather say so — what we offer instead is specificity you can check against the code.

hello@sonela.ai sonela.ai