PostgreSQL-first SQL security gate for AI-generated queries, migrations, and automation workflows.
DBwall reviews PostgreSQL SQL before it reaches a database or merge target. It is intentionally PostgreSQL-only and focuses on high-risk statements: destructive DDL/DML, risky privilege changes, and suspicious bulk-access patterns. The repository is named DBwall; the CLI binary is dbguard.
AI-generated SQL is often syntactically valid but operationally unsafe. DBwall is meant to catch statements such as:
- unbounded
DELETEandUPDATE - destructive schema operations like
DROP TABLE,DROP SCHEMA, and safety-boundary removal - privilege expansion such as
GRANT ... TO PUBLIC - suspicious reads or exports from protected objects
It returns one of three decisions:
allowwarnblock
DBwall is a SQL review gate, not a database firewall or a substitute for database permissions.
- It does not execute queries or enforce runtime access control.
- It does not claim full semantic understanding of every PostgreSQL migration pattern.
- It does not support non-PostgreSQL dialects.
- In
corecoverage mode it deliberately keeps reduced advanced-rule coverage rather than pretending parity with the parser-backed path.
Exit codes:
| Code | Meaning |
|---|---|
0 |
Allow |
1 |
Tool or parse error |
2 |
Warn |
3 |
Block |
DBwall reports its parser coverage mode explicitly:
full(CGO_ENABLED=1): security metadata is derived by walking the structured PostgreSQL AST frompg_query_go(relations, grants/revokes, predicates, nested CTE/subquery sources). JSON/SARIF also expose per-statementcompleteness.core(CGO_ENABLED=0): portable token parser with explicitly reduced semantic coverage. Joins,UPDATE ... FROM/DELETE ... USINGsecondary relations, and CTE/subquery sources are only accurate infullmode.
Checks that need full mode for accurate relation discovery include:
- join /
UPDATE ... FROM/DELETE ... USINGsecondary relations - CTE and subquery sources (including
COPY (SELECT ...)inner shapes) - constant-folded trivial predicates beyond
TRUEand1 = 1
Both modes handle, at parity: multi-object DROP TABLE / TRUNCATE / GRANT ... ON TABLE a, b, top-level LIMIT / FETCH FIRST n ROWS bounding, subquery-safe WHERE detection (a WHERE inside a SET subquery does not bound an outer mutation), INSERT INTO ... SELECT source relations for simple/comma-separated sources, REVOKE recognition, UTF-8 BOM input, non-ASCII identifiers, and unsupported statement families (they emit the semantic_analysis_incomplete rule instead of silently allowing).
Statements that are recognized as unsupported in both modes — for example DO blocks, CREATE FUNCTION (including SECURITY DEFINER bodies), SET ROLE, SET SESSION AUTHORIZATION, CREATE EXTENSION, transaction wrappers in full mode — produce a warn-level semantic_analysis_incomplete finding by default. They are never silently allowed unless you explicitly set that rule to allow in your policy.
Portable release archives are built with CGO_ENABLED=0 (core mode). Tagged releases also publish a Linux amd64 full-mode archive (dbguard_<tag>_linux_amd64_full.tar.gz, CGO_ENABLED=1) for first-party PR gates.
- Predicate analysis is syntactic constant folding. Column-vs-column tautologies such as
WHERE id = idare treated as non-trivial; DBwall does not claim to prove row-level safety. - Revocations (
REVOKE) are recognized and allowed; no rule reviews revoke scope today. INSERT INTO ... SELECTsource coverage in core mode captures simple and comma-separated sources; joins, subqueries, andVALUES ((SELECT ...))sources require full mode.- In full mode the same staging-copy check (
insert_select_from_protected_table) sees all AST-derived read relations; in core mode it is bounded by the token parser's documented FROM handling.
Release archives are produced by .github/workflows/release.yml:
- Portable archives (core mode,
CGO_ENABLED=0): Linux amd64, macOS amd64/arm64 tarballs, Windows amd64 zip. These containdbguard+ README + LICENSE and reportcoverage_mode=core. - Full-mode archive:
dbguard_<tag>_linux_amd64_full.tar.gzwith aCOVERAGE_MODE.txtmarker, built on GitHub's Ubuntu runners where a C toolchain is available. The release workflow verifies this artifact reportscoverage_mode=fullbefore publishing.
scripts/release_smoke.sh mirrors the portable build steps locally/CI-side: it builds every portable archive, round-trips checksums.txt, checks archive structure, extracts the Windows zip, and runs the produced binary (version output, coverage_mode=core, block decision). CI runs it in the release-smoke job on every push/PR to main. The optional DBWALL_SMOKE_FULL_BIN variable packages any native full-mode binary into the full-archive shape for structural verification on hosts without a Linux C cross-toolchain.
This is the reliable install path for the current repo state.
Portable build:
git clone https://github.com/ChimdumebiNebolisa/DBwall.git
cd DBwall
go build -o dbguard ./cmd/dbguardFull PostgreSQL parser-backed build:
CGO_ENABLED=1 go build -o dbguard ./cmd/dbguardgo install github.com/ChimdumebiNebolisa/DBwall/cmd/dbguard@latestRelease archives are produced by .github/workflows/release.yml when a semver tag such as v0.2.0 is pushed. Use the GitHub Releases page for the currently published version instead of hardcoding a version string from the README.
Build the CLI and review one statement:
go build -o dbguard ./cmd/dbguard
./dbguard review-sql "DELETE FROM users;"Review a file with policy and machine-readable output:
./dbguard review-file ./migrations/latest.sql --policy ./examples/dbguard.yaml --format jsonReview every changed SQL file in one aggregated decision (PR-gate style):
./dbguard review-files ./migrations/001.sql ./migrations/002.sql --policy ./examples/dbguard.yaml --format sarif > dbwall.sarifHuman-readable review:
dbguard review-sql "DELETE FROM users;"JSON for automation:
dbguard review-file ./migrations/latest.sql --policy ./dbguard.yaml --format jsonSARIF for code scanning:
dbguard review-file ./migrations/latest.sql --policy ./dbguard.yaml --format sarif > dbwall.sarifMulti-file SARIF (per-file locations, one aggregated decision):
dbguard review-files ./a.sql ./b.sql --policy ./dbguard.yaml --format sarif > dbwall.sarifVersion:
dbguard versionhuman: concise summary, per-statement findings, rationale, remediation, and coverage-mode notejson: stable machine-readable output with decision, severity, summary, tool/version metadata, and finding detailssarif: code-scanning output for GitHub and similar tooling
DBwall stays additive and PostgreSQL-specific. The policy file supports:
dialectprotected_tablesprotected_schemasprotected_roles- per-rule
allow|warn|blockoverrides inrules
Example:
dialect: postgres
protected_tables:
- users
- payments
protected_schemas:
- finance
protected_roles:
- pg_read_all_data
rules:
delete_without_where: block
truncate_table: block
writes_to_protected_tables: warn
select_without_limit_from_protected_table: warn
insert_select_from_protected_table: warnFull example: examples/dbguard.yaml
- Reusable GitHub Action:
action.yml(inputs:version,policy,sql-paths,fail-on-warn) - GitHub Actions example (changed
.sqlPR gate): examples/GITHUB_ACTION_EXAMPLE.md - Pre-commit: examples/PRE_COMMIT_EXAMPLE.md
- Generic CI: examples/CI_EXAMPLE.md
Repo workflows:
- CI: .github/workflows/ci.yml
- Release: .github/workflows/release.yml
DBwall includes an adversarial corpus under test_e2e/testdata/corpus.json covering:
- good queries
- borderline queries
- obviously dangerous queries
- false-positive cases
- multi-table / CTE / COPY-select / multi-object and incomplete-analysis cases (many require
fullmode)
The reproducible benchmark harness lives under benchmark/.
Run it from the repo root (full mode):
CGO_ENABLED=1 go run ./benchmark/cmd/dbwallbench --repo-root . --manifest ./benchmark/manifest.json --json-out ./benchmark/results/benchmark_results.json --report-out ./benchmark/reports/benchmark_report.mdCore-mode run (same manifest, portable build):
CGO_ENABLED=0 go run ./benchmark/cmd/dbwallbench --repo-root . --manifest ./benchmark/manifest.json --json-out ./benchmark/results/benchmark_results_core.json --report-out ./benchmark/reports/benchmark_report_core.mdSaved artifacts:
- Full-mode raw results: benchmark/results/benchmark_results.json
- Full-mode report: benchmark/reports/benchmark_report.md
- Core-mode raw results: benchmark/results/benchmark_results_core.json
- Core-mode report: benchmark/reports/benchmark_report_core.md
Current saved full-mode run from benchmark/results/benchmark_results.json:
- Corpus:
benchmark/manifest.json - Coverage mode:
full - Total cases:
42 - Correct blocks:
17 - Correct allows:
9 - Correct warns:
16 - False positives:
0 - False negatives:
0 - Precision (
blockas positive class):1.0000 - Recall (
blockas positive class):1.0000 - Accuracy (exact decision match):
1.0000
Current saved core-mode run from benchmark/results/benchmark_results_core.json: 27 cases (the 15 cases marked requires_full are skipped), accuracy 1.0000, precision/recall (block) 1.0000, false positives 0, false negatives 0.
Those numbers are measured results from the saved artifacts, not a generalized product claim. Precision and recall use block as the positive class. Cases marked requires_full are included only when the built binary reports coverage_mode=full. A perfect score on this corpus means the corpus matches current behavior; it is not evidence of universal correctness.
CGO_ENABLED=1 go test ./...
CGO_ENABLED=0 go test ./...
go vet ./...CGO_ENABLED=1 requires a C compiler. On Windows the msys2 mingw64 gcc works when its bin directory is on PATH. Fuzz targets live in internal/parser (FuzzParse); run for a bounded budget with, for example:
CGO_ENABLED=0 go test -fuzz=FuzzParse -fuzztime=30s ./internal/parseraudit/ADVERSARIAL_AUDIT_REPORT.md documents an adversarial security audit (verified findings, rejected hypotheses, residual risks) and audit/CORE_FULL_CAPABILITY_MATRIX.md tracks per-family core/full capability parity.