sqlguard-ninja 0.5.0__tar.gz → 0.6.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 (202) hide show
  1. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/CHANGELOG.md +23 -0
  2. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/PKG-INFO +30 -4
  3. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/README.md +19 -3
  4. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/THIRD_PARTY_NOTICES.md +23 -1
  5. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/IMPLEMENTATION_PROGRESS.md +2 -1
  6. sqlguard_ninja-0.6.0/docs/development/NEXT_ACTIONS.md +6 -0
  7. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/TEST_EVIDENCE.md +3 -0
  8. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/progress.json +8 -5
  9. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/CI_INTEGRATION.md +22 -0
  10. sqlguard_ninja-0.6.0/docs/guides/OBSERVABILITY.md +133 -0
  11. sqlguard_ninja-0.6.0/docs/releases/v0.6.0.md +24 -0
  12. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/security/THREAT_MODEL.md +1 -0
  13. sqlguard_ninja-0.6.0/examples/observability/docker-compose.yml +47 -0
  14. sqlguard_ninja-0.6.0/examples/observability/grafana/provisioning/dashboards/sqlguard.yml +9 -0
  15. sqlguard_ninja-0.6.0/examples/observability/grafana/provisioning/datasources/prometheus.yml +9 -0
  16. sqlguard_ninja-0.6.0/examples/observability/prometheus.yml +6 -0
  17. sqlguard_ninja-0.6.0/examples/observability/smoke_check.py +42 -0
  18. sqlguard_ninja-0.6.0/examples/observability/synthetic_traffic.py +80 -0
  19. sqlguard_ninja-0.6.0/examples/sql/support_queries.sql +22 -0
  20. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/pyproject.toml +7 -2
  21. sqlguard_ninja-0.6.0/src/sqlguard_ninja/_version.py +1 -0
  22. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/cli/main.py +93 -0
  23. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/audit.py +42 -5
  24. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/checkpoint.py +30 -2
  25. sqlguard_ninja-0.6.0/src/sqlguard_ninja/observability/__init__.py +17 -0
  26. sqlguard_ninja-0.6.0/src/sqlguard_ninja/observability/metrics.py +121 -0
  27. sqlguard_ninja-0.6.0/src/sqlguard_ninja/observability/telemetry.py +148 -0
  28. sqlguard_ninja-0.6.0/src/sqlguard_ninja/reporting/sarif.py +195 -0
  29. sqlguard_ninja-0.6.0/tests/data/sarif-schema-2.1.0.json +3389 -0
  30. sqlguard_ninja-0.6.0/tests/unit/test_grafana_dashboard.py +84 -0
  31. sqlguard_ninja-0.6.0/tests/unit/test_observability.py +381 -0
  32. sqlguard_ninja-0.5.0/docs/development/NEXT_ACTIONS.md +0 -6
  33. sqlguard_ninja-0.5.0/src/sqlguard_ninja/_version.py +0 -1
  34. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/.gitignore +0 -0
  35. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/LICENSE +0 -0
  36. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/baselines/native.json +0 -0
  37. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/external/README.md +0 -0
  38. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
  39. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
  40. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
  41. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
  42. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
  43. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
  44. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
  45. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
  46. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
  47. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/cases/09_mysql.yaml +0 -0
  48. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
  49. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
  50. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
  51. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
  52. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
  53. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/policies/retail_saas.yaml +0 -0
  54. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
  55. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
  56. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
  57. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
  58. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/native.json +0 -0
  59. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/native.md +0 -0
  60. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
  61. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
  62. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
  63. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
  64. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
  65. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
  66. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
  67. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
  68. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/native.csv +0 -0
  69. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/native.json +0 -0
  70. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/native.md +0 -0
  71. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
  72. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
  73. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
  74. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/agent-sql-guard.csv +0 -0
  75. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/agent-sql-guard.json +0 -0
  76. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/agent-sql-guard.md +0 -0
  77. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.csv +0 -0
  78. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.json +0 -0
  79. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.md +0 -0
  80. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/native.csv +0 -0
  81. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/native.json +0 -0
  82. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/native.md +0 -0
  83. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/sql-guardrail.csv +0 -0
  84. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/sql-guardrail.json +0 -0
  85. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/benchmarks/results/v1.2.0/sql-guardrail.md +0 -0
  86. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +0 -0
  87. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
  88. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/benchmarks/RESULTS_v1.2.md +0 -0
  89. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/benchmarks/RESULTS_v1.md +0 -0
  90. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
  91. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/DECISION_LOG.md +0 -0
  92. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
  93. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/AGENT_INTEGRATION.md +0 -0
  94. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
  95. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
  96. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/DATABASE_COMPATIBILITY.md +0 -0
  97. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/MCP_INTEGRATION.md +0 -0
  98. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/POLICY_AS_CODE.md +0 -0
  99. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/POLICY_REFERENCE.md +0 -0
  100. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/guides/QUERY_COST.md +0 -0
  101. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
  102. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.1.0.md +0 -0
  103. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.1.1.md +0 -0
  104. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.2.0.md +0 -0
  105. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.3.0.md +0 -0
  106. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.4.0.md +0 -0
  107. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/releases/v0.5.0.md +0 -0
  108. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
  109. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
  110. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/_shared.py +0 -0
  111. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/fastapi/app.py +0 -0
  112. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/github-actions/sql-security.yml +0 -0
  113. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/langchain/guarded_tool_demo.py +0 -0
  114. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/langgraph/real_llm_agent.py +0 -0
  115. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/langgraph/sql_agent_demo.py +0 -0
  116. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/postgresql/plan_inspection_demo.py +0 -0
  117. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/sdk/quickstart.py +0 -0
  118. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/examples/sqlite/text_to_sql_demo.py +0 -0
  119. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/__init__.py +0 -0
  120. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
  121. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
  122. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
  123. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
  124. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
  125. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
  126. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
  127. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
  128. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
  129. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
  130. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
  131. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
  132. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
  133. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
  134. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/cli/config.py +0 -0
  135. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/cli/templates.py +0 -0
  136. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/__init__.py +0 -0
  137. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/analysis.py +0 -0
  138. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/findings.py +0 -0
  139. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/guard.py +0 -0
  140. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/lineage.py +0 -0
  141. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
  142. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
  143. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
  144. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
  145. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
  146. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
  147. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
  148. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
  149. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
  150. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
  151. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
  152. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
  153. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
  154. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/integrations/mysql.py +0 -0
  155. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/mcp_server.py +0 -0
  156. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
  157. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/inheritance.py +0 -0
  158. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/model.py +0 -0
  159. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +0 -0
  160. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/templates/minimal.yaml +0 -0
  161. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +0 -0
  162. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/policy/tools.py +0 -0
  163. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
  164. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
  165. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
  166. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/py.typed +0 -0
  167. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
  168. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
  169. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
  170. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
  171. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
  172. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/src/sqlguard_ninja/tenancy.py +0 -0
  173. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/__init__.py +0 -0
  174. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/conftest.py +0 -0
  175. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/__init__.py +0 -0
  176. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
  177. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_external_adapters.py +0 -0
  178. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_frameworks.py +0 -0
  179. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_mcp_server.py +0 -0
  180. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_mysql.py +0 -0
  181. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/integration/test_postgres.py +0 -0
  182. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/pg_support.py +0 -0
  183. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/property/__init__.py +0 -0
  184. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/property/test_guard_properties.py +0 -0
  185. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/security/__init__.py +0 -0
  186. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/security/test_agent_boundaries.py +0 -0
  187. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/security/test_corpus_native.py +0 -0
  188. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/security/test_tenant_rewrite.py +0 -0
  189. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/__init__.py +0 -0
  190. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/helpers.py +0 -0
  191. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_benchmark_engine.py +0 -0
  192. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_cli.py +0 -0
  193. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_dataset.py +0 -0
  194. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_docs.py +0 -0
  195. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_experimental_dialects.py +0 -0
  196. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_function_policy_mechanics.py +0 -0
  197. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_lineage.py +0 -0
  198. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_mysql_binding.py +0 -0
  199. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_policy.py +0 -0
  200. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_policy_inheritance.py +0 -0
  201. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_policy_tools.py +0 -0
  202. {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.6.0}/tests/unit/test_rules.py +0 -0
@@ -3,6 +3,29 @@
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.6.0] — 2026-10-24
7
+
8
+ ### Added
9
+ * **Observability** (`sqlguard_ninja.observability`, extras `[otel]` and `[prometheus]`):
10
+ * `Telemetry` and `InstrumentedGuard` emit OpenTelemetry spans `sqlguard.checkpoint` and
11
+ `sqlguard.validate`.
12
+ * `GuardMetrics` exports Prometheus counters and histograms for decisions, findings,
13
+ latency, errors, resource-limit rejections, checkpoint outcomes and policy identity.
14
+ * `SQLCheckpoint(telemetry=...)` instruments its guard automatically.
15
+ * **SARIF 2.1.0**: `sqlninja scan PATH... --format sarif|json|text --fail-on deny|review|never`.
16
+ It validates every statement in SQL files, with stable fingerprints. Tests and CI validate the
17
+ output against the OASIS schema.
18
+ * **Grafana dashboard** `deploy/grafana/sqlguard-ninja.json`, and a Prometheus + Grafana +
19
+ synthetic-traffic stack (`examples/observability/`). A new CI job runs that stack.
20
+ * Audit events gain `schema_version` and `rate_limited`. New
21
+ `SQLCheckpoint(principal_hmac_key=...)` option to pseudonymise principals.
22
+
23
+ ### Security
24
+ * `redact_sql` now redacts hex, bit, byte (`E''`), dollar-quoted, Unicode (`U&''`) and
25
+ national (`N''`) strings and whole `INTERVAL` expressions. Previously these could reach
26
+ `sql_redacted` in audit logs. Comments are dropped, and if any literal token survives,
27
+ redacted SQL is withheld. See threat model T19.
28
+
6
29
  ## [0.5.0] — 2026-10-24
7
30
 
8
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sqlguard-ninja
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -30,6 +30,8 @@ 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
32
  Requires-Dist: mcp<3,>=2.3; extra == 'all'
33
+ Requires-Dist: opentelemetry-api>=1.25; extra == 'all'
34
+ Requires-Dist: prometheus-client>=0.20; extra == 'all'
33
35
  Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
34
36
  Requires-Dist: pymysql>=1.1; extra == 'all'
35
37
  Provides-Extra: dev
@@ -37,10 +39,14 @@ Requires-Dist: build>=1.2; extra == 'dev'
37
39
  Requires-Dist: fastapi>=0.110; extra == 'dev'
38
40
  Requires-Dist: httpx>=0.27; extra == 'dev'
39
41
  Requires-Dist: hypothesis>=6.100; extra == 'dev'
42
+ Requires-Dist: jsonschema>=4.20; extra == 'dev'
40
43
  Requires-Dist: langchain-core>=0.3; extra == 'dev'
41
44
  Requires-Dist: langgraph>=0.2; extra == 'dev'
42
45
  Requires-Dist: mcp<3,>=2.3; extra == 'dev'
43
46
  Requires-Dist: mypy>=1.10; extra == 'dev'
47
+ Requires-Dist: opentelemetry-api>=1.25; extra == 'dev'
48
+ Requires-Dist: opentelemetry-sdk>=1.25; extra == 'dev'
49
+ Requires-Dist: prometheus-client>=0.20; extra == 'dev'
44
50
  Requires-Dist: psycopg[binary]>=3.1; extra == 'dev'
45
51
  Requires-Dist: pymysql>=1.1; extra == 'dev'
46
52
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
@@ -58,8 +64,12 @@ Provides-Extra: mcp
58
64
  Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
59
65
  Provides-Extra: mysql
60
66
  Requires-Dist: pymysql>=1.1; extra == 'mysql'
67
+ Provides-Extra: otel
68
+ Requires-Dist: opentelemetry-api>=1.25; extra == 'otel'
61
69
  Provides-Extra: postgres
62
70
  Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
71
+ Provides-Extra: prometheus
72
+ Requires-Dist: prometheus-client>=0.20; extra == 'prometheus'
63
73
  Description-Content-Type: text/markdown
64
74
 
65
75
  # SQLGuard Ninja
@@ -290,6 +300,20 @@ The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cos
290
300
  to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
291
301
  client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
292
302
 
303
+ ## Observability and SARIF
304
+
305
+ ```bash
306
+ python -m pip install "sqlguard-ninja[otel,prometheus]"
307
+ sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif
308
+ ```
309
+
310
+ `Telemetry(tracer=..., metrics=GuardMetrics())` adds OpenTelemetry spans and Prometheus metrics
311
+ to the checkpoint: decisions, findings by rule, latency, errors, resource-limit rejections and
312
+ checkpoint outcomes. By default no signal contains SQL text, literals, request-context values,
313
+ tenant ids or principals, and tests check this with canary values. A Grafana dashboard
314
+ (`deploy/grafana/`) and a synthetic-traffic Prometheus/Grafana stack are included. SARIF output
315
+ validates against the OASIS 2.1.0 schema. See [OBSERVABILITY.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md).
316
+
293
317
  ## Query cost analysis
294
318
 
295
319
  Every validation includes an explainable static score that uses the catalog's
@@ -371,9 +395,9 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
371
395
 
372
396
  ## Roadmap
373
397
 
374
- Next: restricted-function and system-catalog benchmark cases, an independent held-out
375
- corpus, lineage-aware aggregate checks, and tenant predicate push-down. Later: governed
376
- writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
398
+ Next: restricted-function and system-catalog benchmark cases (issue #5), an independent
399
+ held-out corpus (issue #6), an interactive demo and a hardened deployment reference. Later:
400
+ governed writes, and database-verified SQL Server and Oracle support. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
377
401
 
378
402
  ## Links
379
403
 
@@ -391,6 +415,8 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
391
415
  * [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
392
416
  * [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
393
417
  * [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
418
+ * [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
419
+ * [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
394
420
  * [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
395
421
  * [Threat model](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/THREAT_MODEL.md)
396
422
  * [Limits of static SQL analysis and dialect support](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/STATIC_ANALYSIS_LIMITS.md)
@@ -226,6 +226,20 @@ The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cos
226
226
  to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
227
227
  client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
228
228
 
229
+ ## Observability and SARIF
230
+
231
+ ```bash
232
+ python -m pip install "sqlguard-ninja[otel,prometheus]"
233
+ sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif
234
+ ```
235
+
236
+ `Telemetry(tracer=..., metrics=GuardMetrics())` adds OpenTelemetry spans and Prometheus metrics
237
+ to the checkpoint: decisions, findings by rule, latency, errors, resource-limit rejections and
238
+ checkpoint outcomes. By default no signal contains SQL text, literals, request-context values,
239
+ tenant ids or principals, and tests check this with canary values. A Grafana dashboard
240
+ (`deploy/grafana/`) and a synthetic-traffic Prometheus/Grafana stack are included. SARIF output
241
+ validates against the OASIS 2.1.0 schema. See [OBSERVABILITY.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md).
242
+
229
243
  ## Query cost analysis
230
244
 
231
245
  Every validation includes an explainable static score that uses the catalog's
@@ -307,9 +321,9 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
307
321
 
308
322
  ## Roadmap
309
323
 
310
- Next: restricted-function and system-catalog benchmark cases, an independent held-out
311
- corpus, lineage-aware aggregate checks, and tenant predicate push-down. Later: governed
312
- writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
324
+ Next: restricted-function and system-catalog benchmark cases (issue #5), an independent
325
+ held-out corpus (issue #6), an interactive demo and a hardened deployment reference. Later:
326
+ governed writes, and database-verified SQL Server and Oracle support. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
313
327
 
314
328
  ## Links
315
329
 
@@ -327,6 +341,8 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
327
341
  * [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
328
342
  * [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
329
343
  * [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
344
+ * [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
345
+ * [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
330
346
  * [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
331
347
  * [Threat model](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/THREAT_MODEL.md)
332
348
  * [Limits of static SQL analysis and dialect support](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/STATIC_ANALYSIS_LIMITS.md)
@@ -1,7 +1,8 @@
1
1
  # Third-party notices
2
2
 
3
3
  SQLGuard Ninja is licensed under Apache-2.0. **It does not vendor or copy third-party source
4
- code.** The projects below are used as ordinary dependencies, installed by the package
4
+ code.** The only third-party file in the repository is a test fixture, the SARIF schema
5
+ (below). The projects below are used as ordinary dependencies, installed by the package
5
6
  manager under their own licences. Licences were read from installed package metadata on
6
7
  2026-10-24. Always check the version you install.
7
8
 
@@ -21,6 +22,10 @@ manager under their own licences. Licences were read from installed package meta
21
22
  | `langgraph` | langgraph | 1.2.17 | MIT |
22
23
  | `fastapi` | fastapi / starlette | 0.162.0 / 1.9.0 | MIT / BSD-3-Clause |
23
24
  | `postgres` | psycopg (+ psycopg-binary) | 3.3.6 | LGPL-3.0-only |
25
+ | `mcp` | mcp (official MCP Python SDK) | 2.3.0 | MIT |
26
+ | `mysql` | PyMySQL | 1.2.3 | MIT |
27
+ | `otel` | opentelemetry-api | 1.45.1 | Apache-2.0 |
28
+ | `prometheus` | prometheus-client | 0.26.0 | Apache-2.0 AND BSD-2-Clause |
24
29
 
25
30
  psycopg is LGPL-3.0. SQLGuard Ninja only imports it at runtime when the `postgres` extra is
26
31
  installed. It is not modified or bundled, which is compatible with distributing SQLGuard Ninja
@@ -33,6 +38,23 @@ mypy (MIT), build (MIT). `pgserver` is optionally used for local PostgreSQL test
33
38
  declared dependency, and its package metadata on PyPI does not declare a licence. It bundles
34
39
  PostgreSQL binaries (PostgreSQL License).
35
40
 
41
+ Tests also use jsonschema (MIT) and opentelemetry-sdk (Apache-2.0).
42
+
43
+ ## Test fixture: SARIF 2.1.0 JSON schema
44
+
45
+ `tests/data/sarif-schema-2.1.0.json` is the unmodified OASIS SARIF 2.1.0 (Errata 01) schema,
46
+ downloaded from
47
+ https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json
48
+ (SHA-256 `c3b4bb2d6093897483348925aaa73af03b3e3f4bd4ca38cef26dcb4212a2682e`). It is used only to
49
+ validate SARIF output in tests and is not part of the wheel.
50
+
51
+ > Copyright © OASIS Open 2020. All Rights Reserved. This document and translations of it may be
52
+ > copied and furnished to others, and derivative works that comment on or otherwise explain it
53
+ > or assist in its implementation may be prepared, copied, published, and distributed, in whole
54
+ > or in part, without restriction of any kind, provided that the above copyright notice and this
55
+ > section are included on all such copies and derivative works. (OASIS IPR Policy notice; see
56
+ > https://www.oasis-open.org/policies-guidelines/ipr/)
57
+
36
58
  ## External validators used for benchmarking only
37
59
 
38
60
  Installed into isolated virtual environments by `scripts/setup_external_adapters.py`, never
@@ -10,4 +10,5 @@
10
10
  | 2c Opt-in AST tenant-filter rewrite + checkpoint | VERIFIED (local) | b0b5ade | test_tenant_rewrite (32, executes on SQLite, tenants 1-3) | |
11
11
  | 2d Docs, ADR-011/012, v0.3.0 bump | RELEASED v0.3.0 | 198a308 | full gate 1,804 passed, 94% | |
12
12
  | 3 MCP server (stdio, validation-only) | RELEASED v0.4.0 | ecec4da | 9 tests incl. real SDK client; published-package stdio smoke test | version targets adapted |
13
- | 4 MySQL tested dialect; SQL Server/Oracle experimental; compatibility matrix | VERIFIED (local, CI pending) | see PR | full gate 1,881 passed, 94%; 15 MySQL server tests run in CI | sql_mode parser differential found and closed (T18) |
13
+ | 4 MySQL tested dialect; SQL Server/Oracle experimental; compatibility matrix | RELEASED v0.5.0 | de4d03f | full gate 1,881 passed, 94%; CI MySQL 8.4: 15 passed, 0 skipped | sql_mode parser differential found and closed (T18) |
14
+ | 5 Observability: OTel spans, Prometheus metrics, audit schema, SARIF scan, Grafana + synthetic stack | VERIFIED (local, CI pending) | see PR | full gate 1,916 passed, 94% | redaction leak (hex/E''/$$/U&/N''/INTERVAL) found and fixed (T19) |
@@ -0,0 +1,6 @@
1
+ # Next actions (development program)
2
+
3
+ 1. SQL Server/Oracle real-database tests need vendor licence acceptance (maintainer decision); MySQL function/catalog cases stay under #5.
4
+ 2. Merge the v0.6.0 (observability) PR after CI incl. the new observability job; add that job to required checks; release; verify PyPI + fresh `[otel,prometheus]` install.
5
+ 3. Phase 6: interactive demo on the FastAPI backend (three roles, editor, explanations, lineage, benchmark dashboard, browser e2e tests).
6
+ 4. Phases 7-8: Docker/Kubernetes reference, public demo (hosting needs maintainer authorization).
@@ -16,3 +16,6 @@
16
16
  | 2026-10-24 | v0.4.0 release | PR #20 12/12; main CI on ecec4da success; release.yml run 40117676554 success | wheel and sdist on PyPI, both provenance endpoints 200; 0.1.0-0.3.0 unchanged |
17
17
  | 2026-10-24 | v0.4.0 fresh install + MCP stdio | `pip install --no-cache-dir "sqlguard-ninja[mcp]==0.4.0"` (Python 3.13, outside repo, 4th attempt; CDN lag), then SDK `stdio_client`/`ClientSession` against installed `sqlninja mcp` | 4 tools; allow/deny/cross-tenant deny; 100,001-char input `invalid_input`; forged `role` argument has no effect (deny); capabilities hide denied tables, `read_only: true`; no finding detail leaked |
18
18
  | 2026-10-24 | v0.5.0 candidate (MySQL) | full gate (`ruff`, `mypy`, corpus `--check`, `pytest --cov -m "not mysql"` incl. embedded PostgreSQL, build, `twine check --strict`) | 1,881 passed, 94%; twine PASSED; corpus 1.2.0: 1,070 evaluations, 4 adapters regenerated |
19
+ | 2026-10-24 | v0.5.0 release | PR #22 13/13 (MySQL job: 15 passed, 0 skipped); MySQL job added to required checks; main CI 40119298660 on de4d03f success; release.yml 40119480081 success | wheel and sdist on PyPI, both provenance endpoints 200; 0.1.0-0.4.0 unchanged |
20
+ | 2026-10-24 | v0.5.0 fresh install | `pip install --no-cache-dir "sqlguard-ninja[mysql]==0.5.0"` (Python 3.13, outside repo, `-I`; 2nd attempt) | PyMySQL 1.2.3; dialects include mysql; bundled corpus 1.2.0, 26 MySQL cases 0 mismatches; binding and sql_mode refusals; `o.ID` and card column denied. No MySQL server locally, so execution is evidenced by CI only |
21
+ | 2026-10-24 | v0.6.0 candidate (observability) | full gate (`ruff`, `mypy`, corpus `--check`, `pytest --cov -m "not mysql"`, build, `twine check --strict`) | 1,916 passed, 94%; twine PASSED; SARIF schema-valid; canary privacy tests over 13 literal forms |
@@ -62,25 +62,28 @@
62
62
  {
63
63
  "id": 5,
64
64
  "name": "Database dialects",
65
- "status": "VERIFIED",
65
+ "status": "RELEASED",
66
66
  "release": "v0.5.0",
67
67
  "issues": [
68
68
  11
69
69
  ],
70
70
  "evidence": [
71
- "MySQL executor/binding/sql_mode guard, 26 corpus cases; 1,881 local tests; CI MySQL job pending"
71
+ "MySQL executor/binding/sql_mode guard, 26 corpus cases; 1,881 local tests; CI MySQL job pending",
72
+ "v0.5.0 on PyPI with provenance; CI MySQL 8.4 15/15; fresh [mysql] install verified"
72
73
  ],
73
74
  "blockers": []
74
75
  },
75
76
  {
76
77
  "id": 6,
77
78
  "name": "Observability and SARIF",
78
- "status": "NOT_STARTED",
79
- "release": "v0.4.0",
79
+ "status": "VERIFIED",
80
+ "release": "v0.6.0",
80
81
  "issues": [
81
82
  14
82
83
  ],
83
- "evidence": [],
84
+ "evidence": [
85
+ "OTel/Prometheus/SARIF/Grafana + canary privacy tests; 1,916 local tests; CI pending"
86
+ ],
84
87
  "blockers": []
85
88
  },
86
89
  {
@@ -11,6 +11,28 @@ git add benchmarks/baseline.json # baselines change only through reviewed commi
11
11
 
12
12
  `sqlninja benchmark baseline` refuses to run when `CI` is set. CI never updates baselines.
13
13
 
14
+ ## SARIF and GitHub code scanning
15
+
16
+ `sqlninja scan` writes SARIF 2.1.0. Upload it with GitHub's own action, pinned to a commit SHA
17
+ you have reviewed. Uploading needs `security-events: write`, and code scanning must be available
18
+ for the repository.
19
+
20
+ ```yaml
21
+ permissions:
22
+ contents: read
23
+ security-events: write
24
+ steps:
25
+ - uses: actions/checkout@<sha>
26
+ - run: pip install sqlguard-ninja
27
+ - run: sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif --fail-on never
28
+ - uses: github/codeql-action/upload-sarif@<sha>
29
+ with:
30
+ sarif_file: sqlninja.sarif
31
+ ```
32
+
33
+ Results are fingerprinted per file, rule and literal-insensitive query, so changing only a
34
+ literal does not open a new alert. The log contains rule messages but no SQL snippet.
35
+
14
36
  ## GitHub Action
15
37
 
16
38
  See [`examples/github-actions/sql-security.yml`](../../examples/github-actions/sql-security.yml).
@@ -0,0 +1,133 @@
1
+ # Observability
2
+
3
+ SQLGuard Ninja can emit OpenTelemetry traces, Prometheus metrics, structured JSON audit
4
+ events and SARIF reports. All of these are optional, and none are imported by the core.
5
+
6
+ ```bash
7
+ python -m pip install "sqlguard-ninja[otel,prometheus]"
8
+ ```
9
+
10
+ ## Privacy contract
11
+
12
+ By default, **no signal contains**:
13
+
14
+ * SQL text, literal values or comments
15
+ * request context values (for example API keys or other attributes)
16
+ * the tenant id (in traces and metrics)
17
+ * the principal (in traces and metrics)
18
+ * internal finding `detail`
19
+
20
+ Signals carry the decision, rule ids, bounded labels and correlation identifiers. The
21
+ correlation identifiers are the decision id, the policy fingerprint and revision, the
22
+ literal-insensitive query fingerprint and the correlation (trace) id. Exception messages are
23
+ never recorded, because they can quote SQL; spans record only the exception type.
24
+
25
+ `tests/unit/test_observability.py` checks this. It sends queries containing a canary value
26
+ through the checkpoint in many literal forms and checks the canary appears in none of the
27
+ signals. The forms include:
28
+
29
+ * strings, comments, dollar-quoted, `E''`, `U&''`, `N''` and hex strings
30
+ * `INTERVAL` text and MySQL double-quoted strings
31
+ * unterminated and multi-statement input
32
+
33
+ The signals checked are audit JSON, logging output, exported spans and the Prometheus
34
+ exposition. The test also puts the canary in request-context secrets.
35
+
36
+ ## Tracing (OpenTelemetry)
37
+
38
+ ```python
39
+ from opentelemetry import trace
40
+ from sqlguard_ninja import Guard, Policy
41
+ from sqlguard_ninja.integrations import SQLCheckpoint
42
+ from sqlguard_ninja.observability import GuardMetrics, Telemetry
43
+
44
+ telemetry = Telemetry(tracer=trace.get_tracer("my-app"), metrics=GuardMetrics())
45
+ checkpoint = SQLCheckpoint(Guard(Policy.from_file("policy.yaml")), telemetry=telemetry)
46
+ ```
47
+
48
+ The checkpoint instruments its guard automatically. To instrument a guard used on its own,
49
+ call `telemetry.instrument(guard)`. Configure exporters and sampling with the OpenTelemetry
50
+ SDK as usual.
51
+
52
+ | Span | Attributes |
53
+ |---|---|
54
+ | `sqlguard.checkpoint` | `sqlguard.mode`, `.decision`, `.decision_id`, `.authorized`, `.enforced`, `.rate_limited`, `.tenant_filter_rewrite`, `.correlation_id`, `.attempt` |
55
+ | `sqlguard.validate` (child) | `sqlguard.decision`, `.decision_id`, `.role`, `.dialect`, `.policy.name`, `.policy.revision`, `.policy.fingerprint`, `.findings.count`, `.findings.rule_ids`, `.query_fingerprint`; on exceptions `error.type` and an ERROR status |
56
+
57
+ ## Metrics (Prometheus)
58
+
59
+ `GuardMetrics()` uses a private `CollectorRegistry`. To use your own, pass
60
+ `GuardMetrics(registry=...)`. Expose the metrics with `metrics.render()` (returns the body and
61
+ content type) or with `prometheus_client.start_http_server(port, registry=metrics.registry)`.
62
+
63
+ | Metric | Labels | Meaning |
64
+ |---|---|---|
65
+ | `sqlguard_decisions_total` | `decision`, `role`, `dialect`, `policy` | validations |
66
+ | `sqlguard_findings_total` | `rule_id`, `action` | findings per rule |
67
+ | `sqlguard_validation_duration_seconds` | `decision` | histogram |
68
+ | `sqlguard_validation_errors_total` | `error` (exception type) | exceptions during validation |
69
+ | `sqlguard_limit_rejections_total` | `rule_id` | length, AST size, structure and cost limits |
70
+ | `sqlguard_checkpoint_outcomes_total` | `mode`, `outcome` | `allowed`, `blocked`, `approved`, `rejected`, `audit_only`, `rate_limited` |
71
+ | `sqlguard_policy_info` | `policy`, `revision`, `fingerprint` | always 1 |
72
+
73
+ Label cardinality is bounded. A role not defined in the policy is reported as `unknown`.
74
+
75
+ ## Audit events
76
+
77
+ `SQLCheckpoint(audit_sink=...)` writes one `AuditEvent` per decision. It works with
78
+ `JsonlAuditSink`, `LoggingAuditSink`, or your own sink. Each event carries:
79
+
80
+ * `schema_version` (`sqlninja.audit/1`), `decision_id`, `correlation_id`
81
+ * `policy_revision`, `policy_fingerprint`
82
+ * `sql_sha256`, `query_fingerprint`
83
+ * `sql_redacted`: literals become `?` and comments are dropped. It is withheld (`null`) if the
84
+ SQL cannot be parsed, or if any literal token survives redaction.
85
+ * `rate_limited`, plus the rule ids and actions of the findings
86
+
87
+ Audit events do record the principal and tenant, because that is their purpose. Pass
88
+ `principal_hmac_key=` to store an HMAC-SHA256 pseudonym of the principal instead. Treat audit
89
+ logs as sensitive. If a short query embeds a guessable secret, its `sql_sha256` can be
90
+ brute-forced. Use `query_fingerprint` where an identifier is enough.
91
+
92
+ ## SARIF
93
+
94
+ ```bash
95
+ sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif
96
+ ```
97
+
98
+ `scan` validates every statement in `*.sql` files and splits them with the dialect tokenizer.
99
+ Each finding becomes a SARIF result:
100
+
101
+ * located at its statement
102
+ * level by action: `error` for deny, `warning` for review, `note` for warn
103
+ * carrying a `partialFingerprints` entry that is stable when only literals change
104
+
105
+ No code snippet is copied into the log. The output validates against the official OASIS SARIF
106
+ 2.1.0 schema, both in the tests and in CI. To upload to GitHub code scanning, see
107
+ [CI_INTEGRATION.md](CI_INTEGRATION.md).
108
+
109
+ ## Grafana dashboard and synthetic telemetry
110
+
111
+ `deploy/grafana/sqlguard-ninja.json` is a Grafana dashboard. It shows:
112
+
113
+ * decisions per second
114
+ * deny ratio and errors
115
+ * p50/p95/p99 latency
116
+ * top rules
117
+ * checkpoint outcomes
118
+ * resource-limit rejections
119
+ * decisions by role
120
+ * loaded policies
121
+
122
+ To see it with synthetic traffic:
123
+
124
+ ```bash
125
+ docker compose -f examples/observability/docker-compose.yml up -d --wait
126
+ # Grafana (read-only, anonymous): http://127.0.0.1:3000/d/sqlguard-ninja
127
+ # Prometheus: http://127.0.0.1:9090
128
+ ```
129
+
130
+ The traffic generator replays the bundled synthetic corpus through an instrumented
131
+ checkpoint. It executes nothing. CI starts this stack and checks two things: Prometheus has
132
+ scraped `sqlguard_decisions_total`, and Grafana has provisioned the dashboard and can query
133
+ Prometheus.
@@ -0,0 +1,24 @@
1
+ # SQLGuard Ninja v0.6.0
2
+
3
+ ## Highlights
4
+
5
+ * **OpenTelemetry tracing and Prometheus metrics.** Install the optional extras `[otel]` and `[prometheus]`. Pass `SQLCheckpoint(telemetry=Telemetry(tracer=..., metrics=GuardMetrics()))` to get spans for checkpoint and validation decisions. Metrics cover decisions, findings by rule, latency, errors, resource-limit rejections and checkpoint outcomes, all with bounded labels.
6
+ * **Privacy by default.** Spans, metrics and audit events contain no SQL text, literals, comments, request-context values or internal detail. Traces and metrics also omit tenant ids and principals. Canary tests check this across many literal forms.
7
+ * **SARIF 2.1.0.** `sqlninja scan` validates every statement in SQL files and writes SARIF for code scanning. The output validates against the OASIS schema in the tests and in CI.
8
+ * **Grafana dashboard.** It ships with a local Prometheus + Grafana stack fed by synthetic traffic, which CI starts and checks.
9
+
10
+ ## Security fix
11
+
12
+ * Audit redaction previously let hex, bit, `E''`, dollar-quoted, `U&''` and `N''` strings, and `INTERVAL` text, through into `sql_redacted`. All are now redacted, and redacted SQL is withheld if any literal token survives. If you keep audit logs from earlier versions, review them for inlined secrets.
13
+
14
+ ## Notes
15
+
16
+ * All benchmark results are still internal (issues #5 and #6). SQL Server and Oracle remain parser-level experimental.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ python -m pip install "sqlguard-ninja[otel,prometheus]==0.6.0"
22
+ ```
23
+
24
+ PyPI: https://pypi.org/project/sqlguard-ninja/0.6.0/
@@ -58,6 +58,7 @@ flowchart LR
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
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
+ | T19 | Secrets leaking through logs, traces, metrics or SARIF | A model inlines a credential or customer value as a literal or comment, or an exception message quotes SQL | Redaction of every literal form, with comments dropped; output is re-tokenized and withheld if any literal survives; telemetry carries ids and bounded labels only; spans record exception types only; SARIF copies no snippet; canary tests | `sql_sha256` is brute-forceable for short secrets; custom sinks and rules can add their own leaks |
61
62
  | T18 | MySQL parser differential through `sql_mode` | Under `ANSI_QUOTES` or `NO_BACKSLASH_ESCAPES` the server reads a validated string differently (e.g. `"card_number"` as a column, not a string) | `mysql_executor` checks `@@SESSION.sql_mode` and refuses those flags; placeholder values with backslashes are refused | Custom executors must call `check_sql_mode` themselves; global `sql_mode` changes between the check and the statement are not detected |
62
63
  | 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 |
63
64
  | 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 |
@@ -0,0 +1,47 @@
1
+ # Local observability stack with synthetic traffic: Prometheus + Grafana + traffic generator.
2
+ # Synthetic data only; nothing is executed against a database. Ports bind to 127.0.0.1.
3
+ #
4
+ # docker compose -f examples/observability/docker-compose.yml up -d --wait
5
+ # open http://127.0.0.1:3000/d/sqlguard-ninja (anonymous read-only viewer)
6
+ services:
7
+ traffic:
8
+ image: python:3.12-slim
9
+ working_dir: /src
10
+ volumes: ["../..:/src:ro"]
11
+ command: >-
12
+ sh -c "pip install --no-cache-dir --target /tmp/deps '/src[prometheus]' >/dev/null &&
13
+ PYTHONPATH=/tmp/deps python /src/examples/observability/synthetic_traffic.py --host 0.0.0.0 --port 9464"
14
+ healthcheck:
15
+ test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9464/metrics')"]
16
+ interval: 5s
17
+ retries: 60
18
+ read_only: true
19
+ tmpfs: ["/tmp:exec,mode=1777"] # pip installs compiled wheels here
20
+ user: "65534:65534"
21
+ environment: {HOME: /tmp, PIP_CACHE_DIR: /tmp/pip}
22
+ prometheus:
23
+ image: prom/prometheus:v3.13.5@sha256:aafa9d694e6a883c02db516d9f6e5b84e14195f49a2a6456a4cca815edda72b9
24
+ volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml:ro"]
25
+ ports: ["127.0.0.1:9090:9090"]
26
+ depends_on: {traffic: {condition: service_healthy}}
27
+ healthcheck:
28
+ test: ["CMD", "wget", "-qO-", "http://127.0.0.1:9090/-/ready"]
29
+ interval: 5s
30
+ retries: 30
31
+ grafana:
32
+ image: grafana/grafana:13.0.11@sha256:2abd2114bb65e4d6f0614206b7c882c1e7c6a992827ba08396aa5afc23ca07f5
33
+ environment:
34
+ GF_AUTH_ANONYMOUS_ENABLED: "true"
35
+ GF_AUTH_ANONYMOUS_ORG_ROLE: Viewer
36
+ GF_AUTH_DISABLE_LOGIN_FORM: "true"
37
+ GF_USERS_ALLOW_SIGN_UP: "false"
38
+ GF_ANALYTICS_REPORTING_ENABLED: "false"
39
+ volumes:
40
+ - ./grafana/provisioning:/etc/grafana/provisioning:ro
41
+ - ../../deploy/grafana:/var/lib/grafana/dashboards:ro
42
+ ports: ["127.0.0.1:3000:3000"]
43
+ depends_on: {prometheus: {condition: service_healthy}}
44
+ healthcheck:
45
+ test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/api/health"]
46
+ interval: 5s
47
+ retries: 30
@@ -0,0 +1,9 @@
1
+ apiVersion: 1
2
+ providers:
3
+ - name: sqlguard-ninja
4
+ folder: SQLGuard Ninja
5
+ type: file
6
+ disableDeletion: true
7
+ allowUiUpdates: false
8
+ options:
9
+ path: /var/lib/grafana/dashboards
@@ -0,0 +1,9 @@
1
+ apiVersion: 1
2
+ datasources:
3
+ - name: Prometheus
4
+ uid: prometheus
5
+ type: prometheus
6
+ access: proxy
7
+ url: http://prometheus:9090
8
+ isDefault: true
9
+ editable: false
@@ -0,0 +1,6 @@
1
+ global:
2
+ scrape_interval: 5s
3
+ scrape_configs:
4
+ - job_name: sqlguard-synthetic
5
+ static_configs:
6
+ - targets: ["traffic:9464"]
@@ -0,0 +1,42 @@
1
+ """CI smoke check for the observability stack: Prometheus scraped the synthetic metrics
2
+ and Grafana provisioned the dashboard with a working Prometheus datasource."""
3
+
4
+ import json
5
+ import sys
6
+ import time
7
+ import urllib.parse
8
+ import urllib.request
9
+
10
+
11
+ def get(url: str) -> dict:
12
+ with urllib.request.urlopen(url, timeout=10) as r:
13
+ return json.load(r)
14
+
15
+
16
+ def main() -> int:
17
+ q = urllib.parse.quote("sum(sqlguard_decisions_total)")
18
+ for _ in range(40):
19
+ data = get(f"http://127.0.0.1:9090/api/v1/query?query={q}")["data"]["result"]
20
+ if data and float(data[0]["value"][1]) > 0:
21
+ break
22
+ time.sleep(3)
23
+ else:
24
+ print("Prometheus has no sqlguard_decisions_total samples")
25
+ return 1
26
+ dash = get("http://127.0.0.1:3000/api/dashboards/uid/sqlguard-ninja")["dashboard"]
27
+ exprs = [t["expr"] for p in dash["panels"] for t in p["targets"]]
28
+ ds = urllib.parse.quote(
29
+ exprs[0].replace("$__rate_interval", "1m").replace('{policy=~"$policy"}', "")
30
+ )
31
+ proxied = get(
32
+ f"http://127.0.0.1:3000/api/datasources/proxy/uid/prometheus/api/v1/query?query={ds}"
33
+ )
34
+ print(
35
+ f"decisions={data[0]['value'][1]} panels={len(dash['panels'])}",
36
+ f"grafana_query={proxied['status']}",
37
+ )
38
+ return 0 if proxied["status"] == "success" else 1
39
+
40
+
41
+ if __name__ == "__main__":
42
+ sys.exit(main())