非生产唯一边界 / 非唯一边界 — this gate is not the sole production security control. Prefer least-privilege DB roles, network isolation, and human workflows alongside SQLGuard.
| Entrypoint | Role |
|---|---|
CLI check |
Evaluate SQL; no execute |
CLI hook |
PreToolUse: block raw psql / mysql / … |
CLI mcp |
MCP stdio (query_sql / write_sql) |
CLI proxy |
Gate then execute if ALLOW |
CLI approve / resolve / reject |
Human approve / recover (trusted executor + token) |
CLI audit / pending / init / exec |
Ops helpers |
CLI serve |
SQLGuard DataPilot HTTP (/v1/check, /v1/block, /v1/execute, /v1/datapilot) |
sql-write-gate check "SQL" # evaluate SQL; no execute
sql-write-gate hook # PreToolUse: block raw psql/mysql/…
sql-write-gate mcp # MCP stdio (query_sql / write_sql)
sql-write-gate proxy --sql "..." # gate then execute if ALLOW
sql-write-gate approve <id> # human approve then write (once)
sql-write-gate resolve <id> --as succeeded|failed|rejected
sql-write-gate audit # TIME / SOURCE / OP / TABLE / VERDICT
sql-write-gate init # scaffold policy.yaml + catalog.json契约稳定(v1.1+):DataPilot 出站只依赖 BLOCK / EXECUTE(HTTP /v1/check·/v1/block / /v1/execute·/v1/datapilot,以及等价 MCP/CLI)。已合入的 1.1 路径勿破坏字段语义;扩展只加字段、不改既有含义。
DataPilot calls this gate outbound. Prefer MCP query_sql / write_sql, CLI check / exec / proxy, or HTTP:
sql-write-gate serve --host 127.0.0.1 --port 8787 \
--policy policy.yaml --catalog catalog.json --database seed/warehouse.duckdb
# Non-loopback / 0.0.0.0 requires: --auth-token SECRET (or SQL_WRITE_GATE_HTTP_TOKEN)| Method | Path | Behavior |
|---|---|---|
GET |
/healthz |
{ok, product: SQLGuard, version} |
POST |
/v1/check |
Evaluate only → action + risk_score (executed: false) |
POST |
/v1/execute |
Gate then execute only on ALLOW |
POST |
/v1/block |
Alias of /v1/check |
POST |
/v1/datapilot |
Alias of /v1/execute (1.1 semantics unchanged) |
Request JSON: { "sql": "...", "actor"?, "model_id"?, "prompt_summary"? } only. serve locks --policy / --catalog / --database / environment at startup; body overrides of those fields are rejected.
Response always includes action (ALLOW | BLOCK | REQUIRE_APPROVAL), rule_id, reason, risk_score, risk_factors, executed. Treat anything other than ALLOW as non-executing.
- Server-locked at startup.
sql-write-gate serve --policy/--catalog/--database(plus environment from the locked policy) is bound for the process lifetime. Request bodies may only supplysql/actor/model_id/prompt_summary; overrides ofpolicy/catalog/database/db_path/environmentreturn400 trust_boundary_violation. - Auth for non-loopback. Binding
127.0.0.1/::1may omit auth. Non-loopback hosts (including0.0.0.0/::) require--auth-tokenorSQL_WRITE_GATE_HTTP_TOKEN; all-interfaces without auth is refused at startup. PresentAuthorization: Bearer <token>orX-SQLGuard-Token. - GitHub Release lags main. Package /
maintracks the latest commit; GitHub Latest Release may lag. Prefer install-from-main / pin the tip SHA for suite acceptance until a matching Release is cut.
permissions:
enforce: true
tables:
orders: [select, insert, update]
hallucination:
allow_unknown_tables: false
allow_unknown_columns: falsePython alias: from write_gate import sqlguard (product helpers); package/CLI names unchanged.
| operation | rule |
|---|---|
| select | allow |
| insert | approval |
| update | approval |
| delete | block |
| ddl | block |
Limits: update_rows: 100, delete_rows: 50. Demo policy (examples/policy.demo.yaml) allows insert/update for walkthroughs.
Guards (any BLOCK wins, else any APPROVAL, else ALLOW):
destructive → schema → pii → freshness → blast_radius → environment
Public surfaces for SemVer (see compatibility.md):
check · exec · hook · mcp · proxy · approve · resolve · reject · pending · audit · init
from write_gate import WriteGate, Decision, Evidence # Evidence is Decision alias
with WriteGate(database="postgresql://…") as gate:
decision = gate.check("DELETE FROM orders WHERE order_id = 1")
decision, result = gate.execute("SELECT 1")
decision, result = gate.approve(approval_id) # trusted executor + token env
gate.reject(approval_id)Key methods: check, execute, approve, reject, close / context manager.
| Field | Type | Notes |
|---|---|---|
allowed |
bool | True only for ALLOW |
action |
str | ALLOW | BLOCK | REQUIRE_APPROVAL |
risk |
str | low | medium | critical |
rule_id |
str | e.g. ok, delete_without_where, unsupported_sql |
reason / message |
str | Human-readable (same text) |
evidence |
object | Guard evidence map |
sql |
str | Evaluated statement |
operation |
str | null | select / insert / update / delete / ddl |
table |
str | null | Primary table when known |
estimated_rows |
int | null | Blast-radius estimate |
approval_id |
str | null | When queued for approval |
rows / rowcount / truncated |
optional | Present on execute/approve --json when materializing |
ALLOW | BLOCK | REQUIRE_APPROVAL with risk, rule_id, reason, evidence.
| Concern | Where it lives |
|---|---|
DB credentials (DATABASE_URL / …) |
Trusted executor only |
policy.yaml / catalog |
Trusted executor (agents have no rewrite API) |
approve / resolve / reject |
Trusted executor with approval token |
check / hook / MCP query_sql/write_sql |
Agent-facing: evaluate / enqueue only |
- On the trusted executor, create a secret file (default
.logs/approval.key, or setSQL_WRITE_GATE_APPROVAL_KEY_FILE). - When calling
approve/resolve/reject, set envSQL_WRITE_GATE_APPROVAL_TOKENto that file's contents. - Missing key file, missing token, or wrong token → refuse (CLI exit non-zero). Correct token → approve/resolve/reject proceeds.
- Agents must not receive the key file or token. They may still enqueue
REQUIRE_APPROVALvia normal write paths.
Approval records store database_config_id (fingerprint). Approve reconnect binds trusted credentials only for the same target. Queue against DB A then change env to DB B → approve fail closed (will not write to B).
- 非生产唯一边界 / 非唯一边界 — combine with least-privilege DB roles, network isolation, and human workflows
- Not a distributed approval lock, MySQL wire-protocol proxy, or Web UI
- Not an enterprise DQ / lineage / ChatBI / multi-tenant platform
- Not new cloud warehouses beyond the declared DuckDB / PostgreSQL / MySQL / SQLite matrix
| Env | Default | Purpose |
|---|---|---|
SQL_WRITE_GATE_STATEMENT_TIMEOUT_SEC |
0 (off) |
Wall-clock statement timeout for check/execute/approve (caller returns at deadline; abandoned worker may still run → indeterminate/unknown; no blind retry) |
SQL_WRITE_GATE_RESULT_ROW_LIMIT |
1000 |
Cap SELECT/approve rows (truncate + truncated=true) |
SQL_WRITE_GATE_RESULT_BYTE_LIMIT |
0 (off) |
Hard byte cap on materialized rows (payload ≤ limit, or ResultOversizeError when RESULT_OVERSIZE=block; oversized single row never returned intact) |
SQL_WRITE_GATE_RESULT_OVERSIZE |
truncate |
truncate (shrink/omit to keep ≤ byte/row caps) or block (ResultOversizeError) |
SQL_WRITE_GATE_AUDIT_SQL_MODE |
redact |
Audit SQL storage: redact (literal scrub, default), hash (sha256:…), or plain (verbatim) |
SQL_WRITE_GATE_HTTP_TOKEN |
(optional on loopback) | Bearer token for serve; required for non-loopback / 0.0.0.0 binds |
SQL_WRITE_GATE_AUDIT_MAX_BYTES |
10 MiB |
Rotate audit / approvals JSONL by size |
SQL_WRITE_GATE_AUDIT_ROTATE_DAILY |
false |
Also rotate JSONL per UTC day |
SQL_WRITE_GATE_REQUEST_ID |
auto uuid4 | Audit correlation id |
SQL_WRITE_GATE_EXECUTING_TTL_SEC |
120 |
Stuck executing → unknown |
SQL_WRITE_GATE_APPROVAL_TOKEN |
(required for approve) | Presenter token |
SQL_WRITE_GATE_APPROVAL_KEY_FILE |
.logs/approval.key |
Trusted-executor key path |
See also: install.md, support-matrix.md, troubleshooting.md, CHANGELOG.md.