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.
Files changed (168) hide show
  1. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/CHANGELOG.md +32 -0
  2. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/PKG-INFO +38 -1
  3. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/README.md +33 -0
  4. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +19 -0
  5. sqlguard_ninja-0.4.0/docs/development/IMPLEMENTATION_PROGRESS.md +12 -0
  6. sqlguard_ninja-0.4.0/docs/development/NEXT_ACTIONS.md +6 -0
  7. sqlguard_ninja-0.4.0/docs/development/TEST_EVIDENCE.md +15 -0
  8. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/progress.json +16 -9
  9. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/AGENT_INTEGRATION.md +3 -0
  10. sqlguard_ninja-0.4.0/docs/guides/MCP_INTEGRATION.md +77 -0
  11. sqlguard_ninja-0.4.0/docs/guides/POLICY_AS_CODE.md +97 -0
  12. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/POLICY_REFERENCE.md +5 -0
  13. sqlguard_ninja-0.4.0/docs/releases/v0.3.0.md +29 -0
  14. sqlguard_ninja-0.4.0/docs/releases/v0.4.0.md +20 -0
  15. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/security/THREAT_MODEL.md +3 -0
  16. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/pyproject.toml +4 -2
  17. sqlguard_ninja-0.4.0/src/sqlguard_ninja/_version.py +1 -0
  18. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/main.py +148 -1
  19. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/findings.py +4 -0
  20. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/guard.py +4 -0
  21. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/audit.py +8 -0
  22. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/checkpoint.py +37 -4
  23. sqlguard_ninja-0.4.0/src/sqlguard_ninja/mcp_server.py +287 -0
  24. sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/inheritance.py +118 -0
  25. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/policy/model.py +47 -3
  26. sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +31 -0
  27. sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/minimal.yaml +17 -0
  28. sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +53 -0
  29. sqlguard_ninja-0.4.0/src/sqlguard_ninja/policy/tools.py +410 -0
  30. sqlguard_ninja-0.4.0/src/sqlguard_ninja/tenancy.py +160 -0
  31. sqlguard_ninja-0.4.0/tests/integration/test_mcp_server.py +142 -0
  32. sqlguard_ninja-0.4.0/tests/security/test_tenant_rewrite.py +116 -0
  33. sqlguard_ninja-0.4.0/tests/unit/test_policy_inheritance.py +162 -0
  34. sqlguard_ninja-0.4.0/tests/unit/test_policy_tools.py +254 -0
  35. sqlguard_ninja-0.2.0/docs/development/IMPLEMENTATION_PROGRESS.md +0 -7
  36. sqlguard_ninja-0.2.0/docs/development/NEXT_ACTIONS.md +0 -5
  37. sqlguard_ninja-0.2.0/docs/development/TEST_EVIDENCE.md +0 -7
  38. sqlguard_ninja-0.2.0/src/sqlguard_ninja/_version.py +0 -1
  39. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/.gitignore +0 -0
  40. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/LICENSE +0 -0
  41. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/THIRD_PARTY_NOTICES.md +0 -0
  42. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/baselines/native.json +0 -0
  43. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/external/README.md +0 -0
  44. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
  45. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
  46. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
  47. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
  48. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
  49. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
  50. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
  51. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
  52. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
  53. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
  54. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
  55. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
  56. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
  57. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
  58. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/policies/retail_saas.yaml +0 -0
  59. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
  60. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
  61. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
  62. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
  63. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/native.json +0 -0
  64. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/native.md +0 -0
  65. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
  66. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
  67. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
  68. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
  69. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
  70. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
  71. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
  72. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
  73. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.csv +0 -0
  74. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.json +0 -0
  75. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/native.md +0 -0
  76. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
  77. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
  78. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
  79. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
  80. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/benchmarks/RESULTS_v1.md +0 -0
  81. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
  82. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/DECISION_LOG.md +0 -0
  83. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
  84. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
  85. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
  86. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/CI_INTEGRATION.md +0 -0
  87. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/guides/QUERY_COST.md +0 -0
  88. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
  89. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.1.0.md +0 -0
  90. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.1.1.md +0 -0
  91. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/releases/v0.2.0.md +0 -0
  92. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
  93. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
  94. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/_shared.py +0 -0
  95. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/fastapi/app.py +0 -0
  96. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/github-actions/sql-security.yml +0 -0
  97. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langchain/guarded_tool_demo.py +0 -0
  98. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langgraph/real_llm_agent.py +0 -0
  99. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/langgraph/sql_agent_demo.py +0 -0
  100. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/postgresql/plan_inspection_demo.py +0 -0
  101. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/sdk/quickstart.py +0 -0
  102. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/examples/sqlite/text_to_sql_demo.py +0 -0
  103. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/__init__.py +0 -0
  104. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
  105. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
  106. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
  107. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
  108. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
  109. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
  110. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
  111. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
  112. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
  113. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
  114. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
  115. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
  116. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
  117. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
  118. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/config.py +0 -0
  119. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/cli/templates.py +0 -0
  120. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/__init__.py +0 -0
  121. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/analysis.py +0 -0
  122. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/lineage.py +0 -0
  123. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
  124. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
  125. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
  126. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
  127. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
  128. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
  129. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
  130. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
  131. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
  132. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
  133. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
  134. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
  135. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
  136. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
  137. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
  138. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
  139. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
  140. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/py.typed +0 -0
  141. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
  142. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
  143. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
  144. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
  145. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
  146. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/__init__.py +0 -0
  147. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/conftest.py +0 -0
  148. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/__init__.py +0 -0
  149. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
  150. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_external_adapters.py +0 -0
  151. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_frameworks.py +0 -0
  152. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/integration/test_postgres.py +0 -0
  153. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/pg_support.py +0 -0
  154. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/property/__init__.py +0 -0
  155. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/property/test_guard_properties.py +0 -0
  156. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/__init__.py +0 -0
  157. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/test_agent_boundaries.py +0 -0
  158. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/security/test_corpus_native.py +0 -0
  159. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/__init__.py +0 -0
  160. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/helpers.py +0 -0
  161. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_benchmark_engine.py +0 -0
  162. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_cli.py +0 -0
  163. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_dataset.py +0 -0
  164. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_docs.py +0 -0
  165. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_function_policy_mechanics.py +0 -0
  166. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_lineage.py +0 -0
  167. {sqlguard_ninja-0.2.0 → sqlguard_ninja-0.4.0}/tests/unit/test_policy.py +0 -0
  168. {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.2.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": "VERIFIED",
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": "NOT_STARTED",
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": "NOT_STARTED",
49
- "release": "v0.3.0",
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"