The same SuiteQL request, run as four different finance roles. Generated 2026-07-12 02:29 UTC.
Chief Financial Officer
Corporate Controller
Accounts Receivable Clerk
Accounts Payable Clerk
| Request | CFO | Controller | AR | AP |
|---|---|---|---|---|
SELECT name, bank_account, tax_id FROM customer LIMIT 1Read customer bank + tax id | bank='ACCT-2231978967' tax='TAX-395015207' | bank=None tax='TAX-395015207' | bank=None tax=None | role 'AP' has no read grant on 'customer' |
SELECT COUNT(*) AS n FROM billCount AP bills | = 110 | = 110 | role 'AR' has no read grant on 'bill' | = 58 |
SELECT DISTINCT subsidiary_id FROM invoice ORDER BY subsidiary_idDistinct subsidiaries on invoices | subs [1, 2, 3] | subs [1, 2, 3] | subs [1, 2] | role 'AP' has no read grant on 'invoice' |
DELETE FROM invoice WHERE id = 1Write attempt (DELETE) | only SELECT queries are permitted (got 'DELETE') | only SELECT queries are permitted (got 'DELETE') | only SELECT queries are permitted (got 'DELETE') | only SELECT queries are permitted (got 'DELETE') |
SELECT 1; DROP TABLE customerInjection / second statement | multiple statements are not permitted | multiple statements are not permitted | multiple statements are not permitted | multiple statements are not permitted |
SELECT COUNT(*) AS n FROM main.invoiceDirect base-table bypass | = 140 | = 140 | direct access to base table 'invoice' blocked; role 'AR' is | role 'AP' has no read grant on 'invoice' |
Green = allowed · red = denied. Enforcement runs at the database engine (a SQLite authorizer bound to the connected role), so it holds no matter what SuiteQL the AI writes. Every call — allowed or denied — is written to an append-only audit log. The AI is a governed user of the finance data layer, not a way around the controls.