Sonela Security review · one page

Security and read-only validation

Sonela answers questions about your business by querying your own database. This page is for whoever has to approve that: every guarantee below is a property of the code — a grammar, a transaction mode, a database role, a column that does not exist.

Read-only, enforced three times

Three independent layers, each sufficient on its own. All three have to fail together before the assistant can change anything.

1

An allowlist grammar, before the query runs

Model-written SQL is refused unless every part of it is explicitly permitted, so 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. WITH, UNION, TABLE x and FOR UPDATE are refused by name.
  • Catalogue access is unreachable: pg_*, information_schema, dblink, the XML table exporters, current_setting.
  • Schema-qualified names, comma-separated FROM lists and parenthesised join groups are refused — each one evades the rewrite.
2

A READ ONLY transaction, while it runs

It runs inside a READ ONLY transaction under a statement timeout and a row cap — three seconds, fifty rows — then rolls back rather than commits. PostgreSQL refuses a write in that mode whatever reached it.

3

A SELECT-only role, that you create

The credential you give us belongs to a least-privilege role you create — we supply the script, and you may point it at a read replica. This is the layer we do not control, which is exactly why it matters most.

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: every path to a base table goes through that substitution, and a table absent from your manifest is rejected. The tenant value is a bound parameter, and the tenant identity is server-bound from a signed session — the browser cannot assert it, the model never sees it.

What the model wrote

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

What actually ran

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, so a hidden column cannot be selected even by name. The whole statement is then wrapped in a row limit before it executes.

Credentials: encrypted and row-bound — or never held at all

Direct connection

Encrypted with AES-256-GCM under a fresh nonce, with the identity of their own row authenticated into the ciphertext — so a stolen value decrypts in no other row, workspace or restored backup. Decrypted in memory to serve one request; no API returns it, in whole or in part.

Sonela Bridge

A small outbound-only service in your network polls us for work. The stored connection string is null — that absence is the feature, not a promise: a row holding nothing cannot leak or be decrypted by a compromised control plane. We keep only a hash of the bridge key, and both validation and execution run on your side of the firewall.

What Sonela can see — and what it cannot

Sonela is a data processor: questions and result rows transit our service on the way to the AI provider you chose, under your key and your DPA.

Data What actually happens
The question someone types In transit Reaches Sonela, and the AI provider you chose — your key, your DPA. Nothing writes it down.
The rows a query returns In transit Held in memory for one request, sent to that same provider, never written to our disks.
Stored questions, answers or SQL Never A metering row has counts, latency, tokens, provider and model — and no text column at all.
Chat transcripts Never They live in the visitor's browser. We keep none, so we have none to lose or hand over.
Cached answers holding your data Never An answer grounded in a query result is structurally uncacheable.
Your database credentials Encrypted Direct: AES-256-GCM, row-bound, never returned by any API. Never Bridge: not held at all.
Write access to your database Never Three layers refuse it, and the role you create has no write permission.
Your data used to train a model Never We train nothing. Your rows reach only the provider you chose.
On request — not shipped today

Need rows that never transit Sonela at all? Ask about the local data plane — the bridge runs the entire answer loop, including your LLM calls, inside your network.

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 than imply otherwise — what we offer instead is specificity you can check, and a walk through the code behind every line above.

hello@sonela.cloud sonela.cloud