Build1 distinct publisher3 min readPublished
The published code treats INTO and FOR UPDATE as the writes they are, and it walks CTE bodies and union arms for table references that the top-level FROM clause hides. The tenancy fix is the part still to verify.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The denylist is the line to read twice. Insert, Update, Delete, Merge, Create, Drop, Alter, TruncateTable, Grant, Command, Transaction and Set are the entries anyone would write down from memory [7]. Into and Lock are the entries that show someone attacked their own code first: `SELECT * INTO evil FROM orders` writes and `SELECT * FROM orders FOR UPDATE` takes locks, and both parse as `exp.Select`, so both survive a `sql.strip().upper().startswith("SELECT")` check [8]. Two of the fourteen denied node types are there purely because the statement is still, technically, a SELECT [2][1].
After validation the statement is re-rendered from the AST with `comments=False`, and that rendering is what executes [9]. Two properties fall out. Comments never reach the server, which matters because MySQL executes `/*! ... */` [9]. And the text that runs is canonical SQL derived from the tree that was inspected, rather than the string the model produced.
The allowlist check uses `statement.find_all(exp.Table)`, which walks CTEs, subqueries, joins and union arms [10]. `WITH x AS (SELECT * FROM pg_shadow) SELECT * FROM x` has a clean top-level FROM and the real read buried in the CTE body [13]. CTE names are exempted only when unqualified, so a CTE called `users` does not authorize `secret_schema.users`, which the post says is a real test case [11]. Bare names resolve against default schemas, so an entry of `public.orders` also authorizes `orders`, while `hidden.orders` is refused [12].
Tenancy is where the problem stops being syntactic. One `orders` table holds every customer's rows, and the agent looking into Ann's missing orders must not reach Bob's [14]. ChatterMate's case against checking the model's WHERE clause is a list of proof obligations: no OR that widens the predicate, no join that reintroduces unfiltered rows, no correlated subquery that reaches around it, no UNION arm that adds a branch without it [15]. The post says they stopped validating the predicate and started rewriting [16], and the material available stops there. The rewrite lives in the two files the post names [3]; it is not in the prose.
Roughly 400 lines stand between the agent and the data, and the author says the design was wrong twice before it was right [2][17]. For that 400 to mean anything elsewhere, three things have to hold in your stack. Your dialect has to round-trip through sqlglot faithfully, because parse-then-render is only as safe as the generator; a construct the parser accepts and the renderer flattens is a difference between what you checked and what you ran. Your allowlist entries have to be schema-qualified, or default-schema resolution works against you. And your tenant column has to be uniform enough across tables that a rewrite can be applied mechanically, which is a property of your schema, not of the guardrail.
One gap worth naming. The allowlist is bound to the connector [10], and the connector service is one of the two files cited [3], but whether it also caps rows, sets timeouts or holds read-only credentials is not described in the text available.
Ranked by verification strength, evidence, and original report placement.
The post states they stopped validating the predicate and started rewriting; the available text breaks off mid-sentence at that point, so the rewrite implementation is not shown.
ChatterMate describes an AI agent that reads a support ticket, forms a hypothesis, queries the customer's own systems, and writes up what it found with the queries attached as evidence.
The company says the interesting work was not the agent but the roughly 400 lines standing between the agent and the data, and that they got it wrong twice before getting it right.
The code is Apache-2.0 and in the company's repo, in two files: backend/app/services/sql_guardrails.py and backend/app/services/db_connector_service.py.
The post says you cannot write "only generate SELECT statements" in a system prompt and call that a control; they do write it, but it is a hint about intent, not a guarantee about behaviour, because the model reads customer messages and customer messages can contain instructions.
Their stated rule is that every guarantee is enforced outside the model on a parsed AST, not on the string, by code the model cannot talk to.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 4, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Sling's incremental mode leaves five cancelled orders alive in the warehouse1 distinct publisher
build
A file-copy Allure adapter for Katalon, and the history IDs that make retries useful1 distinct publisher
build
TiDB quietly turns primary-key ORDER BY into a TopN, and the fix is not in v8.5.71 distinct publisher
build
Slow Magento reindexes are a price index problem, and raw SQL makes it worse1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Falsifiable on paper, unchecked in practice
Nearly every technical assertion arrives with something a reader could go and test: two file paths, a licence, the sqlglot parse call, the denylist named node by node, and a test whose assertion is quoted rather than summarised. That is a rare posture for a vendor writeup. What keeps the number in the sixties is that all of it is one author describing his own code, and no one in this reporting has opened the repository to confirm the code matches the prose.
One self-reported deployment
'We shipped it anyway' is the entire adoption record. Releasing the guardrails under Apache-2.0 makes reuse possible without demonstrating any of it: no stars, forks, downstream implementers, tenant counts, or query volumes appear, and nobody has reported trying the CTE and union evasions against the released code.
Sober body, overpromising title
The writeup is harder on itself than most: it calls its own system prompt a hint rather than a control, admits two failed designs, and deliberately allows hostile predicates instead of pattern-matching them forever. The stretch is the promise of 'every guardrail' — the masking layer never arrives, the ordering argument stops mid-sentence, and the strongest claim, that OR 1=1 is no longer even interesting, rests on a test the author wrote for code the author wrote.
The vendor defending its own product surface
ChatterMate sells precisely the arrangement under scrutiny — a language model with a live connection to a customer's database — so a meticulous security narrative doubles as the trust argument for the product, and the line about demos that work fine when nobody is attacking is competitive positioning in technical dress. The offsetting fact is not rhetorical: publishing the files under Apache-2.0 and naming them invites the exact scrutiny that would be embarrassing if the code did not hold up.
Specific enough to act on, thin enough to verify first
The mechanics are concrete, internally consistent, and cheap to check against the repository, which is why this sits above the middle rather than below it. Holding it down is simple arithmetic: a single publisher, an author with a commercial stake, no outside review of either the code or the threat model, and a text that stops in the middle of its last technical point.