Skip to main content

Your data, your rules, any agent.

HaltSQL is a policy firewall for LLM-generated SQL. It parses every statement your agents write with the database's real parser, checks tenants, columns and functions against your policy, and returns reasons the model can act on. It never rewrites SQL, and the check itself needs no database credentials.

Using @modelcontextprotocol/server-postgres with Claude Desktop or Cursor? Swap in haltsql-mcp, the safe drop-in replacement.

Works with your agent stack.

46 integrations, one policy file. Every client fails closed and runs only the exact string it verified.

KeycloakIdentity and secretsMicrosoft Entra IDIdentity and secretsOktaIdentity and secretsAuth0Identity and secretsHashiCorp VaultIdentity and secretsAWS Secrets ManagerIdentity and secretsGoogle Secret ManagerIdentity and secretsAmazon S3Audit and observabilityGoogle Cloud StorageAudit and observabilityApache KafkaAudit and observabilityElasticsearchAudit and observabilityOpenSearchAudit and observabilityOpenTelemetryAudit and observabilityPrometheusAudit and observabilityOpen Policy AgentPolicy and CIGitHub ActionsPolicy and CIGitLab CIPolicy and CIpre-commitPolicy and CIOCI registriesPolicy and CIKubernetes operatorDeployHelmDeployTerraformDeployAWSDeployGoogle CloudDeployAzureDeploy

Where HaltSQL sits.

The agent writes SQL. HaltSQL decides whether that exact string may run for this caller. Only an allowed query reaches the database, bound with tenant values the model never saw. A denied query goes back to the agent with codes and hints.

QuerySELECT name, email FROM customers LIMIT 10
Checks
  • StatementSELECT
  • Tablesapp.customers
  • Columnsemail is not readable
  • Tenantno tenant predicate
Verdict—

The agent writes a query for the user’s question.

Write a policy, check a query.

A policy lists what the agent may read and how rows are scoped to a tenant. Check queries against it from the CLI, an SDK, the HTTP sidecar or the proxy.

policy.yaml
apiVersion: haltsql.dev/v1
kind: Policy
metadata: { name: analytics-agent }
search_path: [app]
statements: { allow: [select] }
require_limit: { max: 1000 }
tenancy:
mode: bound
keys: { tenant: "${ctx.tenant_id}" }
tables:
app.orders:
columns: [id, tenant_id, customer_id, status, total_cents, created_at]
tenant_column: tenant_id
app.customers:
columns: [id, tenant_id, name, created_at] # email and phone stay hidden
tenant_column: tenant_id
haltsql verify --policy policy.yaml \
--context '{"tenant_id":"acme"}' \
--sql 'SELECT status, count(*) FROM orders
WHERE tenant_id = $1 GROUP BY status LIMIT 100'
# exit 0: allowed, bind $1 = 'acme'

haltsql verify --policy policy.yaml \
--context '{"tenant_id":"acme"}' \
--sql 'SELECT email FROM customers LIMIT 10'
# exit 1: E_COLUMN_DENIED, E_TENANT_MISSING

Put a policy in front of your agents today.