sqlguard-ninja 0.2.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/CHANGELOG.md +32 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/PKG-INFO +38 -1
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/README.md +33 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +19 -0
- sqlguard_ninja-0.4.0/docs/development/IMPLEMENTATION_PROGRESS.md +12 -0
- sqlguard_ninja-0.4.0/docs/development/NEXT_ACTIONS.md +6 -0
- sqlguard_ninja-0.4.0/docs/development/TEST_EVIDENCE.md +15 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/progress.json +16 -9
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/AGENT_INTEGRATION.md +3 -0
- sqlguard_ninja-0.4.0/docs/guides/MCP_INTEGRATION.md +77 -0
- sqlguard_ninja-0.4.0/docs/guides/POLICY_AS_CODE.md +97 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/POLICY_REFERENCE.md +5 -0
- sqlguard_ninja-0.4.0/docs/releases/v0.3.0.md +29 -0
- sqlguard_ninja-0.4.0/docs/releases/v0.4.0.md +20 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/security/THREAT_MODEL.md +3 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/pyproject.toml +4 -2
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/_version.py +1 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/main.py +148 -1
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/findings.py +4 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/guard.py +4 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/audit.py +8 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/checkpoint.py +37 -4
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/mcp_server.py +287 -0
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/inheritance.py +118 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/policy/model.py +47 -3
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +31 -0
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/minimal.yaml +17 -0
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +53 -0
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/tools.py +410 -0
- sqlguard_ninja-0.4.0/src/sqlguard_ninja/tenancy.py +160 -0
- sqlguard_ninja-0.4.0/tests/integration/test_mcp_server.py +142 -0
- sqlguard_ninja-0.4.0/tests/security/test_tenant_rewrite.py +116 -0
- sqlguard_ninja-0.4.0/tests/unit/test_policy_inheritance.py +162 -0
- sqlguard_ninja-0.4.0/tests/unit/test_policy_tools.py +254 -0
- sqlguard_ninja-0.2.0/docs/development/IMPLEMENTATION_PROGRESS.md +0 -7
- sqlguard_ninja-0.2.0/docs/development/NEXT_ACTIONS.md +0 -5
- sqlguard_ninja-0.2.0/docs/development/TEST_EVIDENCE.md +0 -7
- sqlguard_ninja-0.2.0/src/sqlguard_ninja/_version.py +0 -1
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/.gitignore +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/LICENSE +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/THIRD_PARTY_NOTICES.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/baselines/native.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/external/README.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/policies/retail_saas.yaml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/native.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/native.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.csv +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/benchmarks/RESULTS_v1.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/DECISION_LOG.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/CI_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/QUERY_COST.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.1.0.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.1.1.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.2.0.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/_shared.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/fastapi/app.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/github-actions/sql-security.yml +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langchain/guarded_tool_demo.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langgraph/real_llm_agent.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langgraph/sql_agent_demo.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/postgresql/plan_inspection_demo.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/sdk/quickstart.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/sqlite/text_to_sql_demo.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/config.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/templates.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/analysis.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/lineage.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/py.typed +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/conftest.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_external_adapters.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_frameworks.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_postgres.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/pg_support.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/property/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/property/test_guard_properties.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/test_agent_boundaries.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/test_corpus_native.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/__init__.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/helpers.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_benchmark_engine.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_cli.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_dataset.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_docs.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_function_policy_mechanics.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_lineage.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_policy.py +0 -0
- {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_rules.py +0 -0
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes are documented here. The project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/); before 1.0, minor versions may change the API.
|
|
5
5
|
|
|
6
|
+
## [0.4.0] — 2026-10-24
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
* **MCP server** (`sqlninja mcp`, `sqlguard_ninja.mcp_server`, extra `[mcp]` on the official
|
|
10
|
+
MCP Python SDK 2.x). Tools: `validate_sql`, `explain_policy_decision`,
|
|
11
|
+
`inspect_query_cost`, `get_policy_capabilities`. The role and tenant are fixed server-side,
|
|
12
|
+
there is no execution and no database connection, internal detail is withheld, ungranted
|
|
13
|
+
tables and columns are not disclosed, input is size-capped, and calls are rate-limited.
|
|
14
|
+
* An end-to-end test drives the real server over stdio with the SDK's `ClientSession`.
|
|
15
|
+
* Docs: `docs/guides/MCP_INTEGRATION.md`; threat model T17.
|
|
16
|
+
|
|
17
|
+
## [0.3.0] — 2026-10-24
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
* **Role inheritance** (`extends`): grants merge, explicit denies are sticky, and conflicts,
|
|
21
|
+
unknown parents and cycles are load errors.
|
|
22
|
+
* **Policy metadata and identity:** `metadata.revision/owner/change_ticket`, a policy
|
|
23
|
+
fingerprint, and a per-decision `decision_id`. Audit events add `policy_revision`,
|
|
24
|
+
`policy_fingerprint`, `decision_id`, `correlation_id`, a literal-insensitive
|
|
25
|
+
`query_fingerprint`, and `tenant_filter_rewrite`.
|
|
26
|
+
* **Policy CLI:** `policy template`, `policy diff` (effect classification,
|
|
27
|
+
`--fail-on-relaxation`), `policy simulate` (`--against`, `--fail-on-change`),
|
|
28
|
+
`policy explain` (decision or column access, with inheritance source).
|
|
29
|
+
* **Templates:** `minimal`, `multi_tenant_saas`, `analytics_readonly`.
|
|
30
|
+
* **Opt-in AST tenant-filter rewrite** (`sqlguard_ninja.tenancy`,
|
|
31
|
+
`SQLCheckpoint(rewrite_tenant_filters=True)`) with re-validation and refusal rules (ADR-011).
|
|
32
|
+
* Docs: `docs/guides/POLICY_AS_CODE.md`; ADR-011, ADR-012; threat model T6a/T6b.
|
|
33
|
+
|
|
34
|
+
### Compatibility
|
|
35
|
+
* Policy files without the new fields behave exactly as before. `ValidationResult` gains
|
|
36
|
+
fields with defaults.
|
|
37
|
+
|
|
6
38
|
## [0.2.0] — 2026-10-24
|
|
7
39
|
|
|
8
40
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlguard-ninja
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Security testing, policy enforcement, agent integration, privacy analysis and CI benchmarking for LLM-generated SQL.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Pratik653/SQLGuard_Ninjapy
|
|
6
6
|
Project-URL: Documentation, https://github.com/Pratik653/SQLGuard_Ninjapy/tree/main/docs
|
|
@@ -29,6 +29,7 @@ Provides-Extra: all
|
|
|
29
29
|
Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
30
30
|
Requires-Dist: langchain-core>=0.3; extra == 'all'
|
|
31
31
|
Requires-Dist: langgraph>=0.2; extra == 'all'
|
|
32
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'all'
|
|
32
33
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
|
|
33
34
|
Provides-Extra: dev
|
|
34
35
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
@@ -37,6 +38,7 @@ Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
|
37
38
|
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
38
39
|
Requires-Dist: langchain-core>=0.3; extra == 'dev'
|
|
39
40
|
Requires-Dist: langgraph>=0.2; extra == 'dev'
|
|
41
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'dev'
|
|
40
42
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
41
43
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'dev'
|
|
42
44
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
@@ -50,6 +52,8 @@ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
|
50
52
|
Provides-Extra: langgraph
|
|
51
53
|
Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
|
|
52
54
|
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
|
|
55
|
+
Provides-Extra: mcp
|
|
56
|
+
Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
|
|
53
57
|
Provides-Extra: postgres
|
|
54
58
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
55
59
|
Description-Content-Type: text/markdown
|
|
@@ -246,6 +250,37 @@ Labels also apply to paths inside JSON columns (`profile->'health'->>'allergies'
|
|
|
246
250
|
suggestions are never enforced. This is a technical control, not a certification of GDPR
|
|
247
251
|
compliance. Full semantics: [POLICY_REFERENCE.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_REFERENCE.md).
|
|
248
252
|
|
|
253
|
+
## Policy as code
|
|
254
|
+
|
|
255
|
+
Roles can inherit (`extends`), and explicit denies cannot be relaxed by a child. Every decision
|
|
256
|
+
carries the policy revision, the policy fingerprint and a decision id. Review changes before
|
|
257
|
+
activating them:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
sqlninja policy template multi_tenant_saas --output policy.yaml
|
|
261
|
+
sqlninja policy diff old.yaml policy.yaml --fail-on-relaxation
|
|
262
|
+
sqlninja policy simulate --policy old.yaml --against policy.yaml --cases cases.yaml
|
|
263
|
+
sqlninja policy explain --policy policy.yaml --role support --column customers.email
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Optional: `SQLCheckpoint(..., rewrite_tenant_filters=True)` adds missing tenant filters
|
|
267
|
+
through the AST and executes only the re-validated rewrite. See
|
|
268
|
+
[POLICY_AS_CODE.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_AS_CODE.md).
|
|
269
|
+
|
|
270
|
+
## MCP server
|
|
271
|
+
|
|
272
|
+
Let MCP-compatible agents check SQL against your policy before running it anywhere:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
python -m pip install "sqlguard-ninja[mcp]"
|
|
276
|
+
sqlninja mcp --policy builtin:retail_saas --role support_analyst --tenant 1
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cost` and
|
|
280
|
+
`get_policy_capabilities`. The role and tenant are fixed server-side, the server never connects
|
|
281
|
+
to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
|
|
282
|
+
client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
|
|
283
|
+
|
|
249
284
|
## Query cost analysis
|
|
250
285
|
|
|
251
286
|
Every validation includes an explainable static score that uses the catalog's
|
|
@@ -340,6 +375,8 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
|
|
|
340
375
|
## Documentation
|
|
341
376
|
|
|
342
377
|
* [Policy reference](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_REFERENCE.md)
|
|
378
|
+
* [Policy as code](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_AS_CODE.md)
|
|
379
|
+
* [MCP integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md)
|
|
343
380
|
* [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
|
|
344
381
|
* [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
|
|
345
382
|
* [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
|
|
@@ -190,6 +190,37 @@ Labels also apply to paths inside JSON columns (`profile->'health'->>'allergies'
|
|
|
190
190
|
suggestions are never enforced. This is a technical control, not a certification of GDPR
|
|
191
191
|
compliance. Full semantics: [POLICY_REFERENCE.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_REFERENCE.md).
|
|
192
192
|
|
|
193
|
+
## Policy as code
|
|
194
|
+
|
|
195
|
+
Roles can inherit (`extends`), and explicit denies cannot be relaxed by a child. Every decision
|
|
196
|
+
carries the policy revision, the policy fingerprint and a decision id. Review changes before
|
|
197
|
+
activating them:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
sqlninja policy template multi_tenant_saas --output policy.yaml
|
|
201
|
+
sqlninja policy diff old.yaml policy.yaml --fail-on-relaxation
|
|
202
|
+
sqlninja policy simulate --policy old.yaml --against policy.yaml --cases cases.yaml
|
|
203
|
+
sqlninja policy explain --policy policy.yaml --role support --column customers.email
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Optional: `SQLCheckpoint(..., rewrite_tenant_filters=True)` adds missing tenant filters
|
|
207
|
+
through the AST and executes only the re-validated rewrite. See
|
|
208
|
+
[POLICY_AS_CODE.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_AS_CODE.md).
|
|
209
|
+
|
|
210
|
+
## MCP server
|
|
211
|
+
|
|
212
|
+
Let MCP-compatible agents check SQL against your policy before running it anywhere:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
python -m pip install "sqlguard-ninja[mcp]"
|
|
216
|
+
sqlninja mcp --policy builtin:retail_saas --role support_analyst --tenant 1
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cost` and
|
|
220
|
+
`get_policy_capabilities`. The role and tenant are fixed server-side, the server never connects
|
|
221
|
+
to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
|
|
222
|
+
client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
|
|
223
|
+
|
|
193
224
|
## Query cost analysis
|
|
194
225
|
|
|
195
226
|
Every validation includes an explainable static score that uses the catalog's
|
|
@@ -284,6 +315,8 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
|
|
|
284
315
|
## Documentation
|
|
285
316
|
|
|
286
317
|
* [Policy reference](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_REFERENCE.md)
|
|
318
|
+
* [Policy as code](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/POLICY_AS_CODE.md)
|
|
319
|
+
* [MCP integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md)
|
|
287
320
|
* [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
|
|
288
321
|
* [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
|
|
289
322
|
* [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
|
|
@@ -122,6 +122,25 @@ Extras: `langchain`, `langgraph`, `fastapi`, `postgres` (`psycopg`), `dev`.
|
|
|
122
122
|
Apache-2.0 provides an explicit patent grant, which matters to enterprise adopters. It is
|
|
123
123
|
compatible with all dependencies we use (MIT, BSD, Apache-2.0). No third-party code is vendored.
|
|
124
124
|
|
|
125
|
+
## ADR-011 — Opt-in tenant-filter rewrite (refines ADR-005)
|
|
126
|
+
|
|
127
|
+
**Context.** Enterprise users want missing tenant filters added rather than rejected.
|
|
128
|
+
|
|
129
|
+
**Decision.** `sqlguard_ninja.tenancy.apply_tenant_filters` may add tenant predicates through
|
|
130
|
+
the AST. It is opt-in (`rewrite_tenant_filters=True`), uses bind parameters only, refuses on any
|
|
131
|
+
non-tenant problem or ambiguous ownership, and **re-validates** the rewritten SQL, which is the
|
|
132
|
+
exact string executed. ADR-005's invariant still holds: the validated string and the executed
|
|
133
|
+
string are identical.
|
|
134
|
+
|
|
135
|
+
**Consequences.** The rewritten SQL is the qualified form produced by sqlglot. It is equivalent
|
|
136
|
+
but may look different from the input. Database RLS remains the authoritative control.
|
|
137
|
+
|
|
138
|
+
## ADR-012 — Role inheritance with sticky explicit denies
|
|
139
|
+
|
|
140
|
+
**Decision.** `extends` merges grants. A child cannot relax an inherited explicit deny, and an
|
|
141
|
+
attempt is a load error. `policy diff` works on the effective (inherited) policy and classifies
|
|
142
|
+
relaxations so reviewers and CI can gate them.
|
|
143
|
+
|
|
125
144
|
## ADR-010 — Read-only scope for v0.1
|
|
126
145
|
|
|
127
146
|
**Decision.** v0.1 validates read-only workloads only. All writes are denied. Supporting governed
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Implementation progress
|
|
2
|
+
|
|
3
|
+
| Phase | Status | Commit | Tests | Notes |
|
|
4
|
+
|---|---|---|---|---|
|
|
5
|
+
| 0 Audit and checkpoint files | IMPLEMENTED | 375c52f^ | — | baseline v0.1.1 verified: clean tree, main@5206e96, PyPI 0.1.1 |
|
|
6
|
+
| 1a Benchmark tiers, basis, F1, Wilson CIs, throughput, CSV, hashes | RELEASED v0.2.0 | 375c52f | focused + full gate | |
|
|
7
|
+
| 1b Lineage: aggregate-only through CTEs, tenant push-down, small-cohort through derived scopes | RELEASED v0.2.0 | 8b59a38 | corpus 1.1.0 (21 boundary cases), 11 unit tests | LIN-012 found a real bypass of the small-cohort rule before release; fixed |
|
|
8
|
+
| 2a Role inheritance (sticky denies), revision/fingerprint, decision ids, audit fields | VERIFIED (local) | 2f1919c | test_policy_inheritance (11) | |
|
|
9
|
+
| 2b Policy diff/simulate/explain/templates CLI | VERIFIED (local) | e64859a | test_policy_tools (21) | |
|
|
10
|
+
| 2c Opt-in AST tenant-filter rewrite + checkpoint | VERIFIED (local) | b0b5ade | test_tenant_rewrite (32, executes on SQLite, tenants 1-3) | |
|
|
11
|
+
| 2d Docs, ADR-011/012, v0.3.0 bump | RELEASED v0.3.0 | 198a308 | full gate 1,804 passed, 94% | |
|
|
12
|
+
| 3 MCP server (stdio, validation-only) | VERIFIED (local) | see PR | 9 tests incl. real SDK client | released as v0.4.0 (version targets adapted) |
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Next actions (development program)
|
|
2
|
+
|
|
3
|
+
1. Merge the v0.4.0 (MCP) PR, tag, release, verify PyPI and a fresh install including the `[mcp]` extra and a stdio session.
|
|
4
|
+
2. Phase 4 (v0.5.0): make MySQL a tested dialect against a MySQL 8 service container in CI (#11); SQL Server and Oracle parser-level experimental only; publish a compatibility matrix.
|
|
5
|
+
3. Phase 5: OpenTelemetry/Prometheus extras, SARIF output (#14), Grafana dashboard JSON.
|
|
6
|
+
4. Phases 6-8: playground UI on the FastAPI backend, Docker/Kubernetes reference, public demo (hosting needs maintainer authorization).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Test evidence
|
|
2
|
+
|
|
3
|
+
| Date | Scope | Command | Result |
|
|
4
|
+
|---|---|---|---|
|
|
5
|
+
| 2026-10-24 | Baseline (v0.1.1) | `pytest --cov` | 1,655 passed, 95% (local, Python 3.11) |
|
|
6
|
+
| 2026-10-24 | v0.2.0 candidate | full gate (`ruff`, `mypy`, corpus `--check`, `pytest --cov`, build, `twine check`) | 1,738 passed, 94% branch coverage; twine PASSED |
|
|
7
|
+
| 2026-10-24 | Benchmark, corpus 1.1.0 | `sqlninja benchmark run --dataset builtin:v1` (4 adapters) | see docs/benchmarks/RESULTS_v1.1.md |
|
|
8
|
+
| 2026-10-24 | v0.2.0 PR #17 | CI run on PR | 12/12 jobs green; gate: baseline from main, 4 approved relabels, 0 regressions |
|
|
9
|
+
| 2026-10-24 | v0.2.0 main | CI run 40107938223 on 8b59a38 | success |
|
|
10
|
+
| 2026-10-24 | v0.2.0 publish | release.yml run 40108113801 | build + publish success; wheel and sdist with PEP 740 provenance; 0.1.0 and 0.1.1 unchanged |
|
|
11
|
+
| 2026-10-24 | v0.2.0 fresh install | `pip install --no-cache-dir --index-url https://pypi.org/simple sqlguard-ninja==0.2.0` (Python 3.13, outside repo) | import from site-packages; lineage allow/deny and tenant push-down via CLI; bundled benchmark 1,044 evaluations with CSV, tiers, 14 file hashes |
|
|
12
|
+
| 2026-10-24 | v0.3.0 candidate | full gate | 1,804 passed, 94%; build + twine PASSED; 3 templates in wheel |
|
|
13
|
+
| 2026-10-24 | v0.3.0 release | PR #19 12/12; main CI on 198a308 success; release.yml run 40115952065 success | PyPI files with provenance |
|
|
14
|
+
| 2026-10-24 | v0.3.0 fresh install | pip from PyPI, Python 3.13, outside repo (3rd attempt; CDN lag) | templates, policy check/explain (inheritance), tenant rewrite, decision metadata OK |
|
|
15
|
+
| 2026-10-24 | v0.4.0 candidate (MCP) | tests/integration/test_mcp_server.py | 9 passed, including a real stdio ClientSession |
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"evidence": [
|
|
15
15
|
"tiers, basis, F1, Wilson CIs, CSV, hashes: VERIFIED locally",
|
|
16
|
-
"no independent dataset yet (#6); function/catalog cases absent (#5)"
|
|
16
|
+
"no independent dataset yet (#6); function/catalog cases absent (#5)",
|
|
17
|
+
"v0.2.0 released: tiers/CIs/CSV/hashes; still no independent data"
|
|
17
18
|
],
|
|
18
19
|
"blockers": [
|
|
19
20
|
"external dataset needs licensed independent source (#6)"
|
|
@@ -22,33 +23,39 @@
|
|
|
22
23
|
{
|
|
23
24
|
"id": 2,
|
|
24
25
|
"name": "Lineage-aware privacy",
|
|
25
|
-
"status": "
|
|
26
|
+
"status": "RELEASED",
|
|
26
27
|
"release": "v0.2.0",
|
|
27
28
|
"issues": [
|
|
28
29
|
8,
|
|
29
30
|
9
|
|
30
31
|
],
|
|
31
32
|
"evidence": [
|
|
32
|
-
"corpus 1.1.0 lineage cases + tests/unit/test_lineage.py; full gate 1,738 passed"
|
|
33
|
+
"corpus 1.1.0 lineage cases + tests/unit/test_lineage.py; full gate 1,738 passed",
|
|
34
|
+
"v0.2.0 on PyPI; fresh install verified"
|
|
33
35
|
],
|
|
34
36
|
"blockers": []
|
|
35
37
|
},
|
|
36
38
|
{
|
|
37
39
|
"id": 3,
|
|
38
40
|
"name": "Policy-as-code and multitenancy",
|
|
39
|
-
"status": "
|
|
41
|
+
"status": "RELEASED",
|
|
40
42
|
"release": "v0.3.0",
|
|
41
43
|
"issues": [],
|
|
42
|
-
"evidence": [
|
|
44
|
+
"evidence": [
|
|
45
|
+
"inheritance, diff/simulate/explain/templates, tenant rewrite, audit metadata; 1,804 tests pass locally",
|
|
46
|
+
"v0.3.0 on PyPI; fresh install verified"
|
|
47
|
+
],
|
|
43
48
|
"blockers": []
|
|
44
49
|
},
|
|
45
50
|
{
|
|
46
51
|
"id": 4,
|
|
47
52
|
"name": "MCP server",
|
|
48
|
-
"status": "
|
|
49
|
-
"release": "v0.
|
|
53
|
+
"status": "VERIFIED",
|
|
54
|
+
"release": "v0.4.0",
|
|
50
55
|
"issues": [],
|
|
51
|
-
"evidence": [
|
|
56
|
+
"evidence": [
|
|
57
|
+
"stdio server; 9 tests incl. official SDK ClientSession"
|
|
58
|
+
],
|
|
52
59
|
"blockers": []
|
|
53
60
|
},
|
|
54
61
|
{
|
|
@@ -103,4 +110,4 @@
|
|
|
103
110
|
]
|
|
104
111
|
}
|
|
105
112
|
]
|
|
106
|
-
}
|
|
113
|
+
}
|
|
@@ -19,6 +19,9 @@ LLM ──SQL──▶ SQLCheckpoint.evaluate(sql, RequestContext) ──▶ aut
|
|
|
19
19
|
| `review` | not run | run only with an approval bound to the SQL hash, trace and principal (single use, TTL, no self-approval) | run |
|
|
20
20
|
| `audit` | run, recorded `enforced=False` | run, recorded | run |
|
|
21
21
|
|
|
22
|
+
`rewrite_tenant_filters=True` (off by default) adds missing tenant filters through the AST and
|
|
23
|
+
executes the re-validated rewrite. See [POLICY_AS_CODE.md](POLICY_AS_CODE.md).
|
|
24
|
+
|
|
22
25
|
`max_denied_attempts` (default 5) locks a trace after repeated denials, so a retry loop
|
|
23
26
|
cannot grind through rephrasings.
|
|
24
27
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# MCP integration
|
|
2
|
+
|
|
3
|
+
SQLGuard Ninja ships a [Model Context Protocol](https://modelcontextprotocol.io/) server that
|
|
4
|
+
lets MCP-compatible agents check SQL against your policy **before** they run it anywhere.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
python -m pip install "sqlguard-ninja[mcp]"
|
|
8
|
+
sqlninja mcp --policy policy.yaml --role support --tenant 42 # stdio transport
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Built on the official MCP Python SDK (`mcp` 2.x, `MCPServer`). The bundled reference policy
|
|
12
|
+
works out of the box: `--policy builtin:retail_saas --role support_analyst --tenant 1`.
|
|
13
|
+
|
|
14
|
+
## Tools
|
|
15
|
+
|
|
16
|
+
| Tool | Arguments | Returns |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| `validate_sql` | `sql`, optional `dialect` (`postgres` or `sqlite`) | `decision` (allow/review/deny), findings (rule id, action, message, remediation), `decision_id`, policy revision |
|
|
19
|
+
| `explain_policy_decision` | `sql`, optional `dialect` | the decision plus per-finding lineage (base column, JSON path, labels) and how-to-fix hints |
|
|
20
|
+
| `inspect_query_cost` | `sql`, optional `dialect` | static cost score, thresholds and factors (no execution plan, no database) |
|
|
21
|
+
| `get_policy_capabilities` | — | the tables and columns this identity may use, their access mode (`allow`, `aggregate_only`, `review`), required tenant filter, limits, supported dialects |
|
|
22
|
+
|
|
23
|
+
No tool accepts a role, a tenant, credentials or a connection string. The server has **no
|
|
24
|
+
execution tool** and never connects to a database.
|
|
25
|
+
|
|
26
|
+
## Security model
|
|
27
|
+
|
|
28
|
+
* **Identity is server-side.** `--role` and `--tenant` are fixed when the server starts, by
|
|
29
|
+
whoever writes the client configuration. A model cannot choose its own permissions. Extra
|
|
30
|
+
arguments such as `role` are rejected or ignored, and the end-to-end test checks this.
|
|
31
|
+
* **Least disclosure.** `get_policy_capabilities` lists only tables and columns the role may
|
|
32
|
+
use. Denied tables and columns are not revealed. Internal finding detail is never returned,
|
|
33
|
+
and with `disclosure: generic` lineage is omitted as well.
|
|
34
|
+
* **Limits.** A hard cap of 100,000 characters applies before parsing, the policy's own length
|
|
35
|
+
and AST-size limits still apply, and a per-process rate limit (120 calls per minute by
|
|
36
|
+
default) is enforced. Errors are structured (`{"error": ..., "message": ...}`) and carry no
|
|
37
|
+
stack traces.
|
|
38
|
+
* **Fail closed.** A tenant-scoped role started without `--tenant` gets every tenant-table
|
|
39
|
+
query denied.
|
|
40
|
+
* The server is a validation service. Running the SQL remains your application's job, with a
|
|
41
|
+
least-privilege, read-only database role and row-level security.
|
|
42
|
+
|
|
43
|
+
## Client configuration (stdio)
|
|
44
|
+
|
|
45
|
+
Most MCP clients accept a JSON server definition of this shape. Adapt the command path to
|
|
46
|
+
your environment:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"sqlguard-ninja": {
|
|
52
|
+
"command": "sqlninja",
|
|
53
|
+
"args": ["mcp", "--policy", "/absolute/path/policy.yaml", "--role", "support", "--tenant", "42"]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
One server process serves one identity. To give different users different permissions, start
|
|
60
|
+
one server per identity from your trusted configuration. Never let the model write the client
|
|
61
|
+
configuration.
|
|
62
|
+
|
|
63
|
+
## Remote transports
|
|
64
|
+
|
|
65
|
+
The SDK also supports Streamable HTTP. SQLGuard Ninja does **not** enable it by default,
|
|
66
|
+
because a remote endpoint needs authentication that binds each caller to a role and tenant.
|
|
67
|
+
If you expose it remotely, put it behind your own authenticated gateway, derive the identity
|
|
68
|
+
from the authenticated caller, and keep one identity per server process or route. This setup
|
|
69
|
+
is not covered by the test suite.
|
|
70
|
+
|
|
71
|
+
## Verification
|
|
72
|
+
|
|
73
|
+
`tests/integration/test_mcp_server.py` starts the real server as a subprocess and connects
|
|
74
|
+
with the official SDK's `stdio_client` and `ClientSession`. It checks tool discovery,
|
|
75
|
+
allowed and denied validations, a forged `role` argument, and capability listing. It also
|
|
76
|
+
unit-tests malformed input, oversized input, unsupported dialects, rate limiting, the
|
|
77
|
+
missing-tenant fail-closed path and least disclosure.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Policy as code
|
|
2
|
+
|
|
3
|
+
Policies are versioned files reviewed like code. This guide covers inheritance, metadata,
|
|
4
|
+
templates and the review workflow. Field reference:
|
|
5
|
+
[POLICY_REFERENCE.md](POLICY_REFERENCE.md).
|
|
6
|
+
|
|
7
|
+
## Metadata and identity
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
version: 1 # schema version
|
|
11
|
+
name: shop
|
|
12
|
+
metadata:
|
|
13
|
+
revision: "2026.10.2" # your revision; recorded in every decision and audit event
|
|
14
|
+
owner: data-governance
|
|
15
|
+
change_ticket: SEC-1234
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Every `ValidationResult` and audit event records:
|
|
19
|
+
- `decision_id`: unique per decision
|
|
20
|
+
- `policy_revision`
|
|
21
|
+
- `policy_fingerprint`: a 16-hex-character content hash that changes with any semantic change
|
|
22
|
+
Audit events also record the `correlation_id` (the request trace id) and a `query_fingerprint`,
|
|
23
|
+
which is the SHA-256 of the literal-redacted SQL. Queries that differ only in literal values
|
|
24
|
+
share a fingerprint, and literal values themselves are never logged.
|
|
25
|
+
|
|
26
|
+
## Role inheritance
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
roles:
|
|
30
|
+
tenant_base:
|
|
31
|
+
tenant_scoped: true
|
|
32
|
+
labels: {payment_card: deny}
|
|
33
|
+
support:
|
|
34
|
+
extends: [tenant_base]
|
|
35
|
+
tables: {customers: "*", orders: [id, tenant_id, status]}
|
|
36
|
+
labels: {contact: allow}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
* Grants are merged. Column lists are unioned, and `"*"` wins.
|
|
40
|
+
* Label access and `column_access` override per key, and `limits` come from the nearest role
|
|
41
|
+
that sets them.
|
|
42
|
+
* **Explicit denies are sticky.** A child cannot relax an inherited deny label, deny column
|
|
43
|
+
rule, `deny_columns` entry, or `tenant_scoped: true`. The attempt is a load error (a
|
|
44
|
+
*conflict*), not a silent override.
|
|
45
|
+
* Unknown parents and cycles are load errors. Grants inherited from parents are validated
|
|
46
|
+
against the catalog just like direct grants.
|
|
47
|
+
|
|
48
|
+
`sqlninja policy explain --policy P --role R --column table.column` shows the effective access
|
|
49
|
+
and which role in the chain set each label.
|
|
50
|
+
|
|
51
|
+
## Templates
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
sqlninja policy template # list: minimal, multi_tenant_saas, analytics_readonly
|
|
55
|
+
sqlninja policy template multi_tenant_saas --output policies/policy.yaml
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Reviewing a change before activation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sqlninja policy diff policies/old.yaml policies/new.yaml # every change, classified
|
|
62
|
+
sqlninja policy diff old.yaml new.yaml --fail-on-relaxation # CI: block silent relaxations
|
|
63
|
+
sqlninja policy simulate --policy old.yaml --against new.yaml --cases examples.yaml
|
|
64
|
+
sqlninja policy explain --policy new.yaml --role support --tenant 1 --sql "SELECT ..."
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
* `diff` compares the **effective** policies, after inheritance. It classifies each change as
|
|
68
|
+
`relaxes` (can only allow more), `restricts` or `neutral`. Covered areas: roles, table and
|
|
69
|
+
column grants, `deny_columns`, column rules, label access, `tenant_scoped`, catalog labels
|
|
70
|
+
and tenant columns, function policy, limits, defaults, cost thresholds and disclosure.
|
|
71
|
+
* `simulate` evaluates example queries (`{id, sql, role, context, expect}`) under the current
|
|
72
|
+
policy and, optionally, a candidate. It reports decision changes and expectation mismatches,
|
|
73
|
+
and exits 1 on mismatch, or with `--fail-on-change` when any decision changes.
|
|
74
|
+
* `explain` prints the structured decision: rule ids, messages, internal detail and lineage.
|
|
75
|
+
Use it for operators. Agent-facing messages remain the redacted `agent_message`.
|
|
76
|
+
|
|
77
|
+
Suggested CI step for policy repositories:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
- run: sqlninja policy diff "$BASE_POLICY" policies/policy.yaml --fail-on-relaxation
|
|
81
|
+
- run: sqlninja policy simulate --policy policies/policy.yaml --cases policies/cases.yaml
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Tenant isolation: inspection, rewrite, and the database
|
|
85
|
+
|
|
86
|
+
1. **Inspection (default).** Every scope reading a tenant table must pin it to the context
|
|
87
|
+
tenant. Otherwise the query is denied (see the methodology for the exact semantics).
|
|
88
|
+
2. **Opt-in rewrite.** `SQLCheckpoint(..., rewrite_tenant_filters=True)` or
|
|
89
|
+
`sqlguard_ninja.tenancy.apply_tenant_filters()` adds the missing
|
|
90
|
+
`alias.tenant_column = <bind parameter>` predicates through the AST, then **re-validates**.
|
|
91
|
+
The rewritten string is what gets executed. It refuses when the query has any other
|
|
92
|
+
problem (a forbidden column, another tenant's id, a write), when ownership is ambiguous
|
|
93
|
+
(the preserved side of a RIGHT/FULL join), or when the rewritten SQL fails validation. The
|
|
94
|
+
tenant value is always a bind parameter, never inlined.
|
|
95
|
+
3. **The database (authoritative).** Use PostgreSQL row-level security, or per-tenant
|
|
96
|
+
credentials, as the real boundary. SQL inspection and rewriting are defence in depth. They
|
|
97
|
+
cannot see views, functions or data an attacker reaches by other means.
|
|
@@ -9,6 +9,7 @@ start-up instead of silently changing behaviour. Run `sqlninja policy check --po
|
|
|
9
9
|
```yaml
|
|
10
10
|
version: 1
|
|
11
11
|
name: shop
|
|
12
|
+
metadata: {revision: "2026.10.1", owner: data-governance} # recorded in every decision
|
|
12
13
|
dialect: postgres # default dialect for validate()
|
|
13
14
|
disclosure: names # or "generic": agent messages never name tables/columns
|
|
14
15
|
|
|
@@ -35,6 +36,7 @@ catalog:
|
|
|
35
36
|
|
|
36
37
|
roles:
|
|
37
38
|
support:
|
|
39
|
+
# extends: [base_role] # inherit grants; inherited explicit denies cannot be relaxed
|
|
38
40
|
tables:
|
|
39
41
|
customers: [id, tenant_id, email, email_domain] # or "*", or {columns, deny_columns, column_access}
|
|
40
42
|
labels: {contact: allow, health: deny}
|
|
@@ -83,6 +85,9 @@ cost: {enabled: true, review_score: 50, deny_score: 80, large_table_rows: 100000
|
|
|
83
85
|
|
|
84
86
|
Without roles, an implicit role grants every catalog table and denies every labelled column.
|
|
85
87
|
|
|
88
|
+
Inheritance, templates, `policy diff` / `simulate` / `explain` and the tenant-filter rewrite
|
|
89
|
+
are described in [POLICY_AS_CODE.md](POLICY_AS_CODE.md).
|
|
90
|
+
|
|
86
91
|
## Decisions
|
|
87
92
|
|
|
88
93
|
Each rule emits findings with an action (`deny`, `review`, `warn`). Any deny → **DENY**,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SQLGuard Ninja v0.3.0
|
|
2
|
+
|
|
3
|
+
Enterprise policy-as-code and tenant enforcement.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
* **Role inheritance** with sticky explicit denies: a child role can add permissions, but cannot undo an inherited deny. Conflicts, unknown parents and cycles fail at load time.
|
|
8
|
+
* **Review before activation:** `sqlninja policy diff` classifies every change as relaxes / restricts / neutral (`--fail-on-relaxation` for CI). `policy simulate --against` shows decision changes on example queries, and `policy explain` traces effective access through the inheritance chain.
|
|
9
|
+
* **Auditability:** every decision has a `decision_id`, the policy revision and fingerprint. Audit events add a correlation id, a literal-insensitive query fingerprint and a tenant-rewrite flag.
|
|
10
|
+
* **Templates:** `minimal`, `multi_tenant_saas`, `analytics_readonly`.
|
|
11
|
+
* **Opt-in AST tenant-filter rewrite:** missing tenant pins are added as bind-parameter predicates and the rewrite is re-validated. It is refused on any other problem or on RIGHT/FULL-join ownership ambiguity.
|
|
12
|
+
|
|
13
|
+
## Compatibility
|
|
14
|
+
|
|
15
|
+
Existing policies behave as before; the new fields are optional.
|
|
16
|
+
|
|
17
|
+
## Known limitations
|
|
18
|
+
|
|
19
|
+
* All benchmark results are internal. There is no independent dataset yet (issue #6), and no restricted-function or system-catalog cases (issue #5).
|
|
20
|
+
* The tenant rewrite emits sqlglot-normalised SQL. Database row-level security remains the authoritative tenant control.
|
|
21
|
+
* Only PostgreSQL and SQLite are supported; writes are denied; no guarantee against SQL injection or data leakage.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m pip install sqlguard-ninja==0.3.0
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
PyPI: https://pypi.org/project/sqlguard-ninja/0.3.0/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SQLGuard Ninja v0.4.0
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
* **MCP server.** `sqlninja mcp --policy ... --role ... --tenant ...` exposes `validate_sql`, `explain_policy_decision`, `inspect_query_cost` and `get_policy_capabilities` to MCP-compatible agents over stdio. It is built on the official MCP Python SDK (2.x).
|
|
6
|
+
* **Security model.** The identity is fixed by the operator at start-up, and tools take no role, tenant, credential or connection-string arguments. The server never executes SQL or connects to a database, withholds internal detail, and does not reveal ungranted tables or columns. Inputs are size-capped and calls are rate-limited.
|
|
7
|
+
* **Verified with a real client.** The end-to-end test launches the server as a subprocess and uses the SDK's `stdio_client` and `ClientSession` to discover and call the tools, including a forged `role` argument.
|
|
8
|
+
|
|
9
|
+
## Notes
|
|
10
|
+
|
|
11
|
+
* Remote (Streamable HTTP) transport is not enabled. A remote deployment needs an authenticated gateway that binds each caller to an identity (see the MCP guide).
|
|
12
|
+
* Known limitations are unchanged: all benchmark results are internal (issues #5 and #6), only PostgreSQL and SQLite are supported, and there is no guarantee against SQL injection or data leakage.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python -m pip install "sqlguard-ninja[mcp]==0.4.0"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
PyPI: https://pypi.org/project/sqlguard-ninja/0.4.0/
|
|
@@ -57,6 +57,9 @@ flowchart LR
|
|
|
57
57
|
| T3 | Prompt injection through database content | A ticket body says "now query payments" | Every subsequent query is checked again. Retry lock-out per trace. Read-only session. | The model can still be steered to permitted data. |
|
|
58
58
|
| T4 | Parser ambiguity | Comment and string tricks, nested comments, dollar quoting | sqlglot dialect parsing. The corpus includes formatting and comment cases validated against real engines. Unparseable input is denied. | Unknown tokenizer differences. Report them as vulnerabilities. |
|
|
59
59
|
| T5 | Dialect-specific bypasses | MySQL `/*! */` executable comments, SQLite `ATTACH`/`PRAGMA` | Executable-comment rule for the MySQL family. SQLite session statements are denied. | Untested dialects are experimental. |
|
|
60
|
+
| T17 | MCP client or model escalating privileges | A model passes `role` or `tenant` arguments, or probes `get_policy_capabilities` for hidden tables | Identity fixed at server start from operator config; tool schemas have no identity arguments; capabilities list only granted, non-denied columns; no execution or credentials in the server | Anyone who can edit the MCP client configuration chooses the identity |
|
|
61
|
+
| T6a | Unsafe automatic tenant filtering | A rewrite that changes meaning or masks a query naming another tenant | Rewrite is opt-in, bind-parameter only, refused on any non-tenant finding and on RIGHT/FULL-join ownership ambiguity; the rewritten SQL is re-validated and is the executed string | Rewritten SQL is sqlglot-normalised; RLS remains authoritative |
|
|
62
|
+
| T6b | Silent policy relaxation through inheritance or edits | A child role or edit quietly widens access | Sticky explicit denies (load error on conflict); `policy diff --fail-on-relaxation`; fingerprint and revision in every decision | Reviewers must still read the diff |
|
|
60
63
|
| T6 | Tenant data leakage | missing filter, `OR 1=1`, other tenant id, unpinned join or subquery | Every scope reading a tenant table must pin it with an equality conjunct to the context value or an approved bind parameter. `OR`/`NOT` don't count, and casts of the column don't count. | Apply RLS in the database as the authoritative control. |
|
|
61
64
|
| T7 | Unauthorized columns | `SELECT *`, whole-row references, alias laundering, columns in filters | Schema-based star expansion. Per-scope base-column attribution. Ungranted columns are denied in any clause. | Views and UDFs hide columns. |
|
|
62
65
|
| T8 | Sensitive-data exposure | card numbers, health data in JSON, salary per person | Explicit labels. Deny / aggregate-only / review per role. JSON path labels. Window aggregates count as value-revealing. Small-cohort review. | Inference attacks across queries. |
|
|
@@ -36,8 +36,10 @@ dependencies = [
|
|
|
36
36
|
langchain = ["langchain-core>=0.3"]
|
|
37
37
|
langgraph = ["langgraph>=0.2", "langchain-core>=0.3"]
|
|
38
38
|
fastapi = ["fastapi>=0.110"]
|
|
39
|
+
# Official MCP Python SDK; 2.x renamed FastMCP to MCPServer.
|
|
40
|
+
mcp = ["mcp>=2.3,<3"]
|
|
39
41
|
postgres = ["psycopg[binary]>=3.1"]
|
|
40
|
-
all = ["sqlguard-ninja[langchain,langgraph,fastapi,postgres]"]
|
|
42
|
+
all = ["sqlguard-ninja[langchain,langgraph,fastapi,postgres,mcp]"]
|
|
41
43
|
dev = [
|
|
42
44
|
"sqlguard-ninja[all]",
|
|
43
45
|
"pytest>=8.0",
|
|
@@ -118,5 +120,5 @@ exclude = ["src/sqlguard_ninja/benchmarking/workers/"]
|
|
|
118
120
|
plugins = ["pydantic.mypy"]
|
|
119
121
|
|
|
120
122
|
[[tool.mypy.overrides]]
|
|
121
|
-
module = ["langgraph.*", "langchain_core.*", "psycopg.*", "pgserver.*"]
|
|
123
|
+
module = ["langgraph.*", "langchain_core.*", "psycopg.*", "pgserver.*", "mcp.*"]
|
|
122
124
|
ignore_missing_imports = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|