RowShield

Help / Scans

CLI reference: rowshield scan and rowshield probe

All plansLast reviewed 2026-08-23

The command line runs the same rule engine as the hosted product, pointed at one database or one URL from a terminal. It exists so a pipeline can stand guard, and so you can check a database without connecting it to anything or creating an account.

Everything below describes behaviour you can verify by running help: the usage text is emitted by the binary itself and kept in step with this page.

rowshield scan

scan runs every catalog rule against a database you control. Pass --db-url explicitly or let it fall back to $DATABASE_URL, which makes container usage plain: mount the secret, invoke the command. --ref records a project reference on the snapshot for bookkeeping, --sql prints the generated remediation beneath each finding, and --json swaps the human report for machine-readable output.

The report mirrors the dashboard: each finding carries its rule, severity, object and rationale, and capture warnings appear ahead of the findings so degraded coverage is impossible to miss. Connection strings routed through transaction-mode poolers work as-is, because the driver is configured without prepared statements.

As with hosted scans, the CLI reads pg_catalog and storage.buckets metadata only and never issues a SELECT against your data. Point it at a role with USAGE on the schemas you want covered and nothing else.

rowshield scan  --db-url <postgres-url> [options]

  --db-url <url>    Postgres connection string. Defaults to $DATABASE_URL.
  --ref <ref>       Supabase project ref, recorded on the snapshot.
  --fail-on <sev>   Exit 1 at this severity or above. Default: high.
  --sql             Print generated remediation SQL under each finding.
  --json            Emit machine-readable JSON instead of a report.

Exit codes 0, 1 and 2

Three outcomes matter to a pipeline, and the CLI maps them to three codes. Exit 0: the scan ran and nothing reached the failure threshold. Exit 1: the scan ran and found something at or above --fail-on, which defaults to high. Exit 2: the scan itself could not run, whether from a refused connection, a missing argument or an unknown command.

The distinction earns its keep in CI. An unsafe schema and a broken scanner both fail the job, but they fail for different reasons, and the log tells you which without re-running anything. Worked through: a clean database exits 0; one missing WITH CHECK policy exits 1 under the default threshold and exits 0 under --fail-on critical; a mistyped password exits 2 regardless of schema state.

Threshold names are the standard severities. Pass an unrecognised value and the CLI fails closed, exiting 1 whenever any finding exists, on the principle that a garbled instruction should widen scrutiny rather than narrow it.

rowshield probe

probe takes an app or project URL and repeats the free check from your own machine: discover the anon key, enumerate exposed tables, report what answered unauthenticated requests. Discovery follows the same rules as the hosted probe, bundle scraping within limits and the fifty-table cap included, and pointing it at a bare project URL skips discovery entirely, in which case --anon-key supplies the key because a project URL carries no bundles to read.

--anon-key also short-circuits discovery on app URLs when you already hold the key. --json is available for scripting, and exit codes follow the same convention as scan, with 1 meaning findings exist.

rowshield probe https://your-app.example.com
rowshield probe https://your-app.example.com --anon-key eyJhbGciOi...REDACTED --json

A CI recipe

Install the CLI however suits your pipeline, npm package, container image or this repository driven through tsx, then gate merges on it. Exit 2 fails the job too: a scan that cannot run is signal, not noise, and swallowing it hides exactly the breakage, such as rotated credentials, that you most need to know about.

Two jobs are worth having: one on pull requests against a disposable staging database, and one scheduled nightly against production with --fail-on high. The first catches migrations; the second catches everything else, including the change somebody made directly in the dashboard on a Friday.

steps:
  - uses: actions/checkout@v4
  - name: RowShield scan
    env:
      DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
    run: rowshield scan --db-url "$DATABASE_URL" --fail-on high

Choosing a threshold

The default of high is a considered position, not a placeholder. Critical and high findings mean exposure or broken integrity, which a merge gate should stop; mediums describe cost, which teams legitimately carry mid-migration. Raising the threshold to critical turns the pipeline into an advisory check that still surfaces everything in its output while blocking only on genuine emergencies.

Lowering it to medium is defensible for greenfield projects where performance rules should bite early. Whichever you choose, keep exit 2 fatal: a threshold is a policy about findings, never an excuse to ignore a scanner that cannot connect.

Related questions

Should CI fail on medium findings?
That depends on appetite, and --fail-on medium exists for teams who want it. High is the default because unindexed predicates and bare auth.uid() calls are common in healthy projects mid-migration.
Does the CLI talk to RowShield servers?
No. It links the same engine packages and runs entirely against the target you name; results go to stdout and nowhere else unless you send them somewhere yourself.

Did this answer your question? If not, tell us what is missing — article corrections go straight to the person who maintains it.

RowShield checks 9 rule classes continuously. This article describes shipped behaviour only.