Build1 publisher3 min readPublished
The agent asks, the gateway decides: why read-only is not a security boundary
An open-source Go project puts a JWT-checking gateway and a separate query adjudicator between agents and warehouses. The credential never reaches the model, and neither does the tenant argument.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

What happened
- n0 is an open-source Go platform that sits between AI agents and enterprise data.
- The first version of the idea was to give the agent a database URL, let it write SQL, run the query and send the rows back to the model; the author says it would have made a nice demo and also a terrible system.
- The problem was not only that a model might produce a DROP TABLE: a read-only user can still run a query that scans half a warehouse, hold connections open, join against a table it was never supposed to see, or return far more data than the agent actually needs.
- The author writes that a database password is still a database password, even when it is hidden inside an agent configuration file.
- In n0 the agent gets a small set of useful tools, the gateway owns authentication and tenant context, and a separate query service decides whether the SQL is safe to execute.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A developer writing on dev.to has published n0, an open-source Go platform that sits between AI agents and enterprise data, after concluding that the obvious design (hand the model a database URL, let it write SQL, return the rows) would have made a nice demo and a terrible system [1][2]. The interesting part is the failure analysis, not the repository: according to the author, the risk is not a stray DROP TABLE but a read-only account that scans half a warehouse, holds connections open, joins against a table it was never supposed to see, or returns far more data than the agent actually needs [3].
That is the sentence to take away from the post. Read-only is a control on writes, and nothing else on that list is a write. The author's second point is blunter: a database password is still a database password, even when it is hidden inside an agent configuration file [4]. Possession is the boundary, not the grant attached to it.
The division of labour is explicit. The agent gets a small set of tools, the gateway owns authentication and tenant context, and a separate query service decides whether the SQL is safe to execute [5]. The author's formulation: the agent decides what it wants to ask, the platform decides whether it is allowed to ask it and how the request is executed [6]. Behind the gateway sit a Meta Service holding workspaces, metadata, connections and schema, a Query Engine described as a SQL sandbox with asynchronous jobs and result lifecycle running over NATS JetStream, and a Connection Manager that talks to PostgreSQL, MySQL, ClickHouse and others [7].
The MCP server lives inside the gateway and, per the post, does not open a database connection, inspect credentials, or implement a second query executor; it translates MCP calls into the same internal clients used by the REST API [8]. The stated reason is that the author did not want security rules to slowly diverge between the API path and the AI path [9]. This is the durable bit. Two executors means two policy engines, and the second one is always the one nobody audits.
MCP itself gives a client a standard way to discover and call tools, but the author lists six questions it does not answer: who is calling, which tenant they belong to, which connection they can use, which tables are allowed, whether the query is safe and bounded, and where the result lives while the query runs [10][11]. Those are gateway concerns. The MCP endpoint goes through the same JWT middleware as the REST API, and the tenant ID used for internal requests comes from the verified JWT context rather than a tenant_id argument supplied by the agent, which the public tool does not accept at all [12][13]. Six tools are exposed today; there is no execute_sql_now, no tool that returns a connection string, and no tool that lets the model choose a different tenant [14][15].
Execution is asynchronous. submit_query takes a connection_id and SQL and returns a job_id with status pending; the agent then polls get_query_status and pages results through get_query_result, which the author argues fits analytical work better than holding one HTTP request open while a warehouse works [16][17][18]. Paging is also where over-fetching can be capped, since the platform, not the model, controls how much comes back.
Watch the adjudication layer. The post names the bounding question but describes the Query Engine only as a sandbox with async jobs and a result lifecycle, and the author says plainly that this is working software rather than a claim that every production concern has been solved [7][19]. Also watch the token model: $TOKEN can be a user JWT or an agent token issued by n0, and agent tokens are where scoping and revocation will either hold or quietly leak [20].