Architecture
Rowsel has two components: a Flutter app and a Rust proxy. The proxy is the execution boundary between the device and the database.
Data flow
Section titled “Data flow” Device (Flutter) Rust proxy Your database ───────────────── ───────────── ─────────────── credentials in ──HTTPS──▶ read-only policy ──▶ PostgreSQL secure storage page-size limits (direct, or via virtualized grid ◀──────── statement timeout ◀── SSH bastion) error normalization- The app sends the connection details and SQL to the proxy over HTTPS.
- The proxy opens a short-lived connection (or a pooled session), runs the statement under guardrails, and returns a normalized JSON response.
- Credentials are used only for the duration of the request or session — the proxy does not persist them.
The proxy
Section titled “The proxy”Built in Rust with axum (HTTP), sqlx (PostgreSQL), russh (native SSH tunneling), and a SQL parser for statement classification. It is stateless for stateless requests, and keeps a small session registry for pooled connections and transactions.
Guardrails enforced at the proxy boundary:
- Read-only classification — the query endpoint accepts a single statement
classified as a read. Data-modifying CTEs,
SELECT INTO,SELECT … FOR UPDATE, and multi-statement batches are rejected. - Read-only transaction — read queries run inside
BEGIN READ ONLYwith aSET LOCAL statement_timeout. - Enforced paging — user SQL is wrapped as a subquery to apply
LIMIT/OFFSET, with a default page size of 200 and a maximum of 500. - Timeouts and body limits — default 10s / max 30s statement timeout, and a request body cap.
The app
Section titled “The app”A Flutter app for mobile and tablet: secure on-device credential storage, a virtualized DataGrid, schema explorer, query editor, and local query history. The same proxy contract serves the app and any other client (for example a web tool calling the proxy from inside your network).
See the Proxy API reference for the full contract.