Tutorial: prove your table is private before you ship
Take the Quickstart notes table, check who can read and change it with a simulation, catch a risky policy before it goes live, and make the check part of your deploys.
What you will build: a repeatable check that proves anonymous visitors can't read or change your notes table, and that signed-in users only reach their own notes — run by hand, on a draft change, and before every deploy.
Why: row rules are easy to get almost right. A single using (true) makes every note public, and nothing in your app will show it until someone looks. A simulation looks for you, as every kind of caller, without touching data.
Prerequisites
- The project and
notestable from the Quickstart (with row-level security enabled and forced). - A Pro plan or higher — simulation runs are not included in Free (Plan limits).
- Python 3.10+ for the CLI:
pip install sovaria-cli, thensov login.
1. Run a simulation for the table
sov sim run --resource public.notesWhat to look for: for Not signed in (__guest__), read, add, update and delete should all be *Denied (expected)*. For Signed-in user, they should be *Allowed* — the row rule then limits them to the user's own notes. The summary ends with the number of findings; there should be no critical ones.
The same result is in the console under Simulation → System map: pick Step by step, choose *Not signed in*, then the Read chip on notes, and press Simulate this request to see the path stop at the policy check.
2. Preview a risky change
Suppose someone proposes this to "fix" an empty list on a public page:
create policy "anyone reads notes" on notes
for select using (true);Try it without applying it. The script runs with every check and is rolled back; the answer lists what it would add — here, a new row policy on notes.
sov sim change --sql-file risky.sqlBecause using (true) applies to everyone, including visitors, this policy would make every note readable without signing in. Don't apply it; if a page needs public content, put that content in a separate table with its own rules.
3. Check every deploy
--fail-on critical exits with status 1 when a run has critical findings, so it can stop a pipeline. In CI the CLI reads its token from SOVARIA_ACCESS_TOKEN (store it as a CI secret; use a team account set aside for CI) and takes the organization and project as flags.
jobs:
check-access:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install sovaria-cli
- run: sov sim run --org "$ORG_ID" --project "$PROJECT_ID" --fail-on critical
env:
SOVARIA_ACCESS_TOKEN: ${{ secrets.SOVARIA_ACCESS_TOKEN }}
ORG_ID: ${{ vars.SOVARIA_ORG_ID }}
PROJECT_ID: ${{ vars.SOVARIA_PROJECT_ID }}Verify
sov sim run --resource public.notesshows no critical findings, and visitors are denied on every action.- In the console, Simulation → Issues lists nothing for
notes. - The CI job fails if you temporarily apply the risky policy in a staging environment and run it there (
--env <environment-id>).
Common errors
| You see | Why | Fix |
|---|---|---|
PLAN_FEATURE_REQUIRED / PLAN_LIMIT_REACHED | Simulations aren't in your plan or this month's runs are used. | Upgrade or wait for the monthly reset — see Plans, usage and limits. |
| Signed-in users denied too | RLS is enabled but not forced, or no access policy decides. | Run alter table notes force row level security; — see Row-level security and access. |
401 in CI | No token, or it expired. | Refresh the SOVARIA_ACCESS_TOKEN secret. |
Production notes
- Simulation never writes data, so it is safe against production — but run it on staging first when you are changing rules.
- Every run, including single-request checks, counts towards the monthly simulation limit.
- Keep the CI token's account to the smallest team role that can run simulations.