Skip to main content

CLI reference

HaltSQL ships two executables:

  • haltsql: verify SQL, debug denials, validate and convert documents, run policy tests.
  • haltsql-server: the HTTP sidecar (see http-api.md).

Every command accepts --help.

Exit codes​

CodeMeaning
0Success. For verify/explain: the query is allowed. For validate/test: everything passed.
1For verify/explain: the query is denied. For test: at least one case failed. For validate: at least one document is invalid.
2Usage error, or a policy, config, context, or input file could not be loaded.

A shadow-mode policy always yields allowed: true, so verify exits 0; would-be denials appear in shadow_reasons.

haltsql verify​

Check one SQL statement against a policy.

haltsql verify --policy FILE
[--context JSON | --context-file FILE]
[--sql SQL | --file FILE]
[--json]
haltsql verify --policy FILE --batch [--jobs N] [--context JSON | --context-file FILE]
FlagMeaning
-p, --policy FILEPolicy document (YAML or JSON). Required.
-c, --context JSONContext as a JSON object, for example '{"tenant_id":"acme"}'. Values are strings, integers, or arrays of those.
--context-file FILERead the context from a YAML or JSON file.
-s, --sql SQLThe SQL string to check.
-f, --file FILERead the SQL string from a file.
--jsonPrint the verdict as JSON (see verdict.md). Without it, ALLOWED or DENIED is printed with the reasons, hints, and values to bind.
--batchRead JSONL requests from stdin and write one JSON verdict per line.
-j, --jobs NWith --batch: verify on N OCaml domains in parallel. Output order is preserved. Default 1.

If neither --sql nor --file is given, the SQL is read from stdin. If no context is given, the context is empty, and any policy with tenancy keys will deny with E_CONTEXT_MISSING.

Examples:

haltsql verify --policy policy.yaml --context '{"tenant_id":"acme"}' \
--sql 'SELECT id FROM orders WHERE tenant_id = $1 LIMIT 10'

echo 'SELECT id FROM orders LIMIT 10' | \
haltsql verify --policy policy.yaml --context-file ctx.json --json

In scripts, branch on the exit code:

if haltsql verify --policy policy.yaml --context-file ctx.json --file q.sql; then
echo "allowed"
else
case $? in 1) echo "denied" ;; *) echo "error" ;; esac
fi

Batch mode​

--batch checks many statements with one process. Each input line is a JSON object:

{"id": "q1", "sql": "SELECT id FROM orders WHERE tenant_id = $1 LIMIT 10", "context": {"tenant_id": "acme"}}
{"id": "q2", "sql": "SELECT id FROM orders LIMIT 10", "context": {"tenant_id": "acme"}}
FieldRequiredMeaning
idyesAny JSON string or number; echoed back so you can match results.
sqlyesThe SQL string.
contextnoContext for this line. If omitted, the --context / --context-file value is used.

Each output line is the compact verdict JSON with "id" as the first field, in input order (also with --jobs). Blank lines are skipped:

{"id":"q1","allowed":true,"reasons":[],"bind_params":[{"param":1,"value":"acme"}], ...}
{"id":"q2","allowed":false,"reasons":[{"code":"E_TENANT_MISSING", ...}], ...}

A line that cannot be read (invalid JSON, missing sql, invalid context) produces {"id": ..., "error": "message"} and does not stop the batch. In batch mode the exit code is 0 once all lines are processed, whatever the verdicts, and 2 if the policy or default context cannot be loaded; check each line's allowed field.

haltsql explain​

Same inputs as verify (except --batch and --json), but prints a human-readable report for debugging a denial:

haltsql explain --policy FILE [--context JSON | --context-file FILE] [--sql SQL | --file FILE]

The report shows:

  • the verdict and the policy name and version;
  • the statement kind;
  • the tables and columns the statement touches;
  • each reason with its code, message, position in the SQL (with a caret under the offending text when the position is known), and the hint;
  • the values to bind, when allowed in bound mode.

Exit codes are the same as verify. The output format is for people and may change between releases; use verify --json in scripts.

haltsql validate​

Check one or more documents of any kind. The kind is detected from the kind field: Policy, Config, TestSuite, Catalog, or Scenario.

haltsql validate FILE...

All errors are printed with file, line, column, and key path. Exit code 0 if all documents are valid, 1 if any is invalid, 2 if a file cannot be read.

haltsql convert​

Convert a document between YAML and JSON without changing its meaning.

haltsql convert FILE --to json|yaml [-o OUT]

The result is written to stdout, or to OUT with -o/--output. The input is validated first; an invalid document is not converted. A converted policy has the same policy_version as the original.

haltsql schema export​

Write the JSON Schemas (draft 2020-12) for every document kind, for editor autocomplete and CI validation.

haltsql schema export --out DIR

Writes files such as policy.schema.json, config.schema.json, catalog.schema.json, and scenario.schema.json into DIR (created if needed).

haltsql test​

Run policy test suites (kind: TestSuite).

haltsql test SUITE_FILE... [--filter TAG] [-v]

--filter TAG runs only cases tagged TAG; -v/--verbose also shows the reasons for passing cases.

Each case is checked against the suite's policy. A case passes when the verdict matches expect, every code in codes appears in the reasons, and (when given) bind equals the returned bind_params. Failing cases are printed with the reason. Exit code 0 if all cases pass, 1 if any fail, 2 if a suite or its policy cannot be loaded. See testing.md for the format.

haltsql lint-policy​

Report risky or suspicious settings in a policy: tables exposing all columns, unused tenancy keys, shadow mode, a missing require_limit, large function allowlists, and tables missing from the catalog. Exits 0 unless the policy fails to load (2).

haltsql lint-policy FILE

haltsql config show​

Print the effective configuration after merging the config file, HALTSQL_* environment variables, and defaults. Secrets are replaced by "<redacted>". ${vault:...}, ${aws-sm:...} and ${gcp-sm:...} placeholders are resolved from the config's secrets section first (see secrets.md); haltsql validate does the same for kind: Config documents.

haltsql config show --config FILE

(--effective is accepted for compatibility; the output is always the merged result.)

haltsql schema pull / schema import / schema fetch​

haltsql schema check -p POLICY (--dsn DSN | --snapshot FILE) [--strict] [--json] compares a policy with the live schema and exits 1 on drift (a stale catalog:, missing tables or columns, a shadowing relation); see schema-and-pii.md.

Snapshot a live Postgres catalog into a Catalog document (schema pull, read-only, TLS and SCRAM-SHA-256), import PII labels from dbt, DataHub, Unity Catalog, Atlan or Collibra exports (schema import), or fetch them from a live data catalog API (schema fetch; the token comes from an environment variable or a file, never the command line). See schema-and-pii.md.

haltsql schema pull --dsn postgres://... -o catalog.json
haltsql schema import --from dbt|datahub|unity|atlan|collibra FILE -o pii.json
haltsql schema fetch --from PROVIDER --url https://... [--token-env VAR | --token-file FILE] \
[--service S] [--database D] [--schema S]... -o pii.json [--snapshot-out fetched.json]

--snapshot-out writes a non-authoritative snapshot (metadata.authoritative: false): policy draft accepts it and drafts without catalog: (so SELECT * stays denied), and a policy catalog: refuses it.

haltsql policy draft​

Write a reviewable starter policy from a catalog snapshot and PII manifest: explicit column lists without PII columns, tenant columns, and require_limit. See schema-and-pii.md.

haltsql rls generate / rls check​

Generate idempotent PostgreSQL role, grant and row-level-security DDL from a policy (rls generate), and report drift between a live database and the policy (rls check; exit 1 on drift). See rls-generation.md.

haltsql bundle​

Build, sign (Ed25519), verify and inspect signed policy bundles: bundle keygen, bundle build, bundle sign, bundle verify, bundle inspect; publish and fetch them as OCI artifacts with bundle push --oci REF and bundle pull --oci REF (exit code 3 when the registry is unreachable or refuses the request; see policy-bundles.md). The server loads bundles listed under policies and refuses any whose signature does not verify. See policy-bundles.md and, for CI, policy-ci.md.

haltsql report​

Shadow-mode analytics over audit records written by haltsql-server or haltsql-proxy (files, directories, or -), and conservative policy suggestions (--suggest), optionally applied to a copy of a policy (--apply-to FILE -o OUT). Exit 1 if applying would turn a tenancy denial into an allow or the patched policy does not validate. See shadow-reports.md.

haltsql report audit.jsonl --since 7d --suggest --policy-file policies/shop.yaml

haltsql audit verify​

Verify the hash chain of audit logs: files, rotated sets (--concat), mixed container logs (--mixed), downloaded object-sink directories (with --spool), and s3://bucket/prefix read directly. Exit 0 when every chain verifies, 1 when one is broken, 2 on usage or input errors. See audit-verify.md.

haltsql audit verify /var/log/haltsql/audit.jsonl
haltsql audit verify --json s3://corp-haltsql-audit/haltsql/audit/

haltsql version​

Print the HaltSQL version, the Postgres grammar version of the vendored libpg_query, and the OCaml version. --json prints them as JSON.

haltsql version [--json]

haltsql-server​

Run the HTTP sidecar.

haltsql-server --config FILE [flags]
haltsql-server --policy FILE [--policy FILE ...] [flags]

At least one of --config or --policy is required.

FlagMeaning
--config FILEConfig document (see config-reference.md).
-p, --policy FILELoad a policy file (repeatable). Added to the config's policies; a policy with the same name replaces the config's.
--listen HOST:PORTOverride server.listen (default 127.0.0.1:8080).
--allow-unauthenticatedAccept requests without a bearer token. Local development only.
--default-policy NAMEPolicy used by unauthenticated requests.
--log-level LEVELdebug, info, warn, or error.
--log-format FORMATjson or text. Logs go to stderr.
--audit SINKstdout, file:PATH, or none (repeatable; replaces the config's sinks).
--log-raw-sqlInclude the raw SQL in audit records.
--watch / --no-watchReload when policy or config files change (polled every 2 seconds).
--max-request-bytes NMaximum request body size (HTTP 413 above). A body whose declared Content-Length is over the limit is rejected before any of it is read, and before authentication.
--max-connections NMaximum open client connections (default 1024). At the limit the server stops accepting and new clients wait in the listen backlog. The --health-listen port has its own budget of 64.
--request-timeout SECONDSTime allowed to receive a request's head and body once its first byte arrives (default 30). Request heads are capped at 64 KiB.
--idle-timeout SECONDSHow long a keep-alive connection may stay silent, and how long a response write may stall (default 60).
--shutdown-timeout SECONDSHow long SIGTERM waits for in-flight requests to finish (default 15).
--no-metricsDisable GET /metrics.
--health-listen HOST:PORTExtra plain-HTTP listener serving only /healthz and /readyz (for probes when the main port requires client certificates).

Flags override HALTSQL_* environment variables, which override the config file. The server reloads all policies atomically on SIGHUP (keeping the last good set on error), and shuts down gracefully on SIGTERM or SIGINT. Exit code 0 after a graceful shutdown, 2 if the configuration cannot be loaded or the listener cannot start.

Quick local start:

haltsql-server --policy policy.yaml --listen 127.0.0.1:8080 --allow-unauthenticated

For anything beyond localhost, turn on built-in TLS with server.tls (see Authentication and TLS), or terminate TLS in a proxy or service mesh in front of the server.