sqlguard-ninja 0.6.0__tar.gz → 0.7.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 (220) hide show
  1. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/.gitignore +4 -0
  2. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/CHANGELOG.md +33 -0
  3. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/PKG-INFO +28 -2
  4. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/README.md +22 -1
  5. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/THIRD_PARTY_NOTICES.md +8 -1
  6. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +13 -0
  7. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/IMPLEMENTATION_PROGRESS.md +3 -1
  8. sqlguard_ninja-0.7.0/docs/development/NEXT_ACTIONS.md +5 -0
  9. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/TEST_EVIDENCE.md +3 -0
  10. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/progress.json +18 -11
  11. sqlguard_ninja-0.7.0/docs/guides/DEPLOYMENT.md +122 -0
  12. sqlguard_ninja-0.7.0/docs/releases/v0.7.0.md +22 -0
  13. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/security/THREAT_MODEL.md +1 -0
  14. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/pyproject.toml +4 -1
  15. sqlguard_ninja-0.7.0/src/sqlguard_ninja/_version.py +1 -0
  16. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/main.py +18 -0
  17. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/app.py +195 -0
  18. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/examples.py +222 -0
  19. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/roles.yaml +55 -0
  20. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/sandbox.py +146 -0
  21. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/service.py +316 -0
  22. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/app.js +490 -0
  23. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/data/benchmark.json +573 -0
  24. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/favicon.svg +1 -0
  25. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/index.html +147 -0
  26. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/styles.css +220 -0
  27. sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/worker.js +38 -0
  28. sqlguard_ninja-0.7.0/tests/e2e/conftest.py +84 -0
  29. sqlguard_ninja-0.7.0/tests/e2e/test_demo_e2e.py +165 -0
  30. sqlguard_ninja-0.7.0/tests/e2e/test_static_demo_e2e.py +80 -0
  31. sqlguard_ninja-0.7.0/tests/unit/__init__.py +0 -0
  32. sqlguard_ninja-0.7.0/tests/unit/test_demo.py +274 -0
  33. sqlguard_ninja-0.6.0/docs/development/NEXT_ACTIONS.md +0 -6
  34. sqlguard_ninja-0.6.0/src/sqlguard_ninja/_version.py +0 -1
  35. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/LICENSE +0 -0
  36. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/baselines/native.json +0 -0
  37. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/external/README.md +0 -0
  38. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
  39. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
  40. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
  41. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
  42. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
  43. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
  44. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
  45. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
  46. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
  47. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/09_mysql.yaml +0 -0
  48. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
  49. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
  50. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
  51. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
  52. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
  53. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/policies/retail_saas.yaml +0 -0
  54. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
  55. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
  56. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
  57. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
  58. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.json +0 -0
  59. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.md +0 -0
  60. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
  61. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
  62. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
  63. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
  64. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
  65. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
  66. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
  67. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
  68. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.csv +0 -0
  69. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.json +0 -0
  70. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.md +0 -0
  71. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
  72. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
  73. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
  74. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.csv +0 -0
  75. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.json +0 -0
  76. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.md +0 -0
  77. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.csv +0 -0
  78. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.json +0 -0
  79. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.md +0 -0
  80. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.csv +0 -0
  81. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.json +0 -0
  82. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.md +0 -0
  83. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.csv +0 -0
  84. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.json +0 -0
  85. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.md +0 -0
  86. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
  87. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.2.md +0 -0
  88. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.md +0 -0
  89. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
  90. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/DECISION_LOG.md +0 -0
  91. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
  92. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/AGENT_INTEGRATION.md +0 -0
  93. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
  94. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
  95. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/CI_INTEGRATION.md +0 -0
  96. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/DATABASE_COMPATIBILITY.md +0 -0
  97. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/MCP_INTEGRATION.md +0 -0
  98. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/OBSERVABILITY.md +0 -0
  99. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_AS_CODE.md +0 -0
  100. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_REFERENCE.md +0 -0
  101. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/QUERY_COST.md +0 -0
  102. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
  103. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.0.md +0 -0
  104. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.1.md +0 -0
  105. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.2.0.md +0 -0
  106. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.3.0.md +0 -0
  107. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.4.0.md +0 -0
  108. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.5.0.md +0 -0
  109. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.6.0.md +0 -0
  110. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
  111. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
  112. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/_shared.py +0 -0
  113. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/fastapi/app.py +0 -0
  114. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/github-actions/sql-security.yml +0 -0
  115. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langchain/guarded_tool_demo.py +0 -0
  116. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langgraph/real_llm_agent.py +0 -0
  117. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langgraph/sql_agent_demo.py +0 -0
  118. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/docker-compose.yml +0 -0
  119. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/grafana/provisioning/dashboards/sqlguard.yml +0 -0
  120. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/grafana/provisioning/datasources/prometheus.yml +0 -0
  121. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/prometheus.yml +0 -0
  122. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/smoke_check.py +0 -0
  123. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/synthetic_traffic.py +0 -0
  124. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/postgresql/plan_inspection_demo.py +0 -0
  125. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sdk/quickstart.py +0 -0
  126. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sql/support_queries.sql +0 -0
  127. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sqlite/text_to_sql_demo.py +0 -0
  128. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/__init__.py +0 -0
  129. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
  130. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
  131. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
  132. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
  133. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
  134. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
  135. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
  136. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
  137. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
  138. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
  139. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
  140. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
  141. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
  142. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
  143. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/config.py +0 -0
  144. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/templates.py +0 -0
  145. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/__init__.py +0 -0
  146. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/analysis.py +0 -0
  147. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/findings.py +0 -0
  148. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/guard.py +0 -0
  149. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/lineage.py +0 -0
  150. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
  151. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
  152. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
  153. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
  154. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
  155. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
  156. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
  157. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
  158. {sqlguard_ninja-0.6.0/tests → sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo}/__init__.py +0 -0
  159. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
  160. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/audit.py +0 -0
  161. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/checkpoint.py +0 -0
  162. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
  163. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
  164. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
  165. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
  166. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/mysql.py +0 -0
  167. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/mcp_server.py +0 -0
  168. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/__init__.py +0 -0
  169. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/metrics.py +0 -0
  170. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/telemetry.py +0 -0
  171. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
  172. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/inheritance.py +0 -0
  173. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/model.py +0 -0
  174. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +0 -0
  175. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/minimal.yaml +0 -0
  176. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +0 -0
  177. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/tools.py +0 -0
  178. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
  179. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
  180. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
  181. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/py.typed +0 -0
  182. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
  183. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
  184. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
  185. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
  186. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
  187. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/sarif.py +0 -0
  188. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/tenancy.py +0 -0
  189. {sqlguard_ninja-0.6.0/tests/integration → sqlguard_ninja-0.7.0/tests}/__init__.py +0 -0
  190. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/conftest.py +0 -0
  191. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/data/sarif-schema-2.1.0.json +0 -0
  192. {sqlguard_ninja-0.6.0/tests/property → sqlguard_ninja-0.7.0/tests/integration}/__init__.py +0 -0
  193. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
  194. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_external_adapters.py +0 -0
  195. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_frameworks.py +0 -0
  196. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mcp_server.py +0 -0
  197. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mysql.py +0 -0
  198. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_postgres.py +0 -0
  199. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/pg_support.py +0 -0
  200. {sqlguard_ninja-0.6.0/tests/security → sqlguard_ninja-0.7.0/tests/property}/__init__.py +0 -0
  201. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/property/test_guard_properties.py +0 -0
  202. {sqlguard_ninja-0.6.0/tests/unit → sqlguard_ninja-0.7.0/tests/security}/__init__.py +0 -0
  203. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_agent_boundaries.py +0 -0
  204. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_corpus_native.py +0 -0
  205. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_tenant_rewrite.py +0 -0
  206. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/helpers.py +0 -0
  207. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_benchmark_engine.py +0 -0
  208. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_cli.py +0 -0
  209. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_dataset.py +0 -0
  210. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_docs.py +0 -0
  211. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_experimental_dialects.py +0 -0
  212. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_function_policy_mechanics.py +0 -0
  213. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_grafana_dashboard.py +0 -0
  214. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_lineage.py +0 -0
  215. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_mysql_binding.py +0 -0
  216. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_observability.py +0 -0
  217. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy.py +0 -0
  218. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_inheritance.py +0 -0
  219. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_tools.py +0 -0
  220. {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_rules.py +0 -0
@@ -28,3 +28,7 @@ htmlcov/
28
28
  !.env.example
29
29
  audit*.jsonl
30
30
  .pytest_tmp/
31
+
32
+ # Static demo build output and download cache
33
+ /.cache/
34
+ /site/
@@ -3,6 +3,39 @@
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.7.0] — 2026-10-24
7
+
8
+ ### Added
9
+ * **Interactive playground** (`sqlguard_ninja.demo`, extra `[demo]`, `sqlninja demo serve`).
10
+ It runs on a synthetic retail database with three enforceable roles: Sales Analyst,
11
+ Customer Support and Store Manager. Features:
12
+ * an SQL editor and 20 example queries; tests check each one's expected decision
13
+ * plain-language explanations with how-to-fix hints
14
+ * lineage (output → base column, column usage with access per role)
15
+ * tenant-isolation demonstrations and a role-permission browser
16
+ * a benchmark dashboard that separates the 1,070 internal evaluations from the 0
17
+ independent ones
18
+ * **Restricted sandbox** for allowed demo queries. The database enforces it: an authorizer
19
+ allowlist, `query_only`, engine limits, a step/time budget and a row cap.
20
+ * **Static build** (`scripts/build_static_demo.py`). The same service runs in the browser with
21
+ Pyodide. Everything is self-hosted and checksum-verified, and the site makes no third-party
22
+ requests.
23
+ * **Deployment references:**
24
+ * a hardened `Dockerfile`: non-root, digest-pinned base, healthcheck
25
+ * a Compose file: read-only, no capabilities, resource limits
26
+ * Kubernetes manifests: restricted security context, probes, NetworkPolicy without
27
+ egress, PDB, example Ingress
28
+ * a manual GitHub Pages workflow
29
+ * **Server hardening:**
30
+ * validated `SQLNINJA_DEMO_*` settings
31
+ * per-client rate limiting, a body cap and 411 on chunked bodies
32
+ * strict CSP and security headers
33
+ * `/healthz` and `/readyz`
34
+ * **Browser end-to-end tests** (Playwright) in server mode and against the offline static
35
+ build, including axe-core accessibility checks, keyboard use, small screens and XSS-safe
36
+ rendering. New CI jobs cover these tests and the container and manifests.
37
+ * ADR-014, threat model T20, `docs/guides/DEPLOYMENT.md`.
38
+
6
39
  ## [0.6.0] — 2026-10-24
7
40
 
8
41
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sqlguard-ninja
3
- Version: 0.6.0
3
+ Version: 0.7.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
@@ -34,6 +34,10 @@ Requires-Dist: opentelemetry-api>=1.25; extra == 'all'
34
34
  Requires-Dist: prometheus-client>=0.20; extra == 'all'
35
35
  Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
36
36
  Requires-Dist: pymysql>=1.1; extra == 'all'
37
+ Requires-Dist: uvicorn>=0.30; extra == 'all'
38
+ Provides-Extra: demo
39
+ Requires-Dist: fastapi>=0.110; extra == 'demo'
40
+ Requires-Dist: uvicorn>=0.30; extra == 'demo'
37
41
  Provides-Extra: dev
38
42
  Requires-Dist: build>=1.2; extra == 'dev'
39
43
  Requires-Dist: fastapi>=0.110; extra == 'dev'
@@ -53,6 +57,7 @@ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
53
57
  Requires-Dist: pytest>=8.0; extra == 'dev'
54
58
  Requires-Dist: ruff>=0.6; extra == 'dev'
55
59
  Requires-Dist: types-pyyaml; extra == 'dev'
60
+ Requires-Dist: uvicorn>=0.30; extra == 'dev'
56
61
  Provides-Extra: fastapi
57
62
  Requires-Dist: fastapi>=0.110; extra == 'fastapi'
58
63
  Provides-Extra: langchain
@@ -300,6 +305,26 @@ The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cos
300
305
  to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
301
306
  client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
302
307
 
308
+ ## Interactive playground
309
+
310
+ A web app on a synthetic retail database. It has:
311
+
312
+ * three enforceable roles (Sales Analyst, Customer Support, Store Manager)
313
+ * example queries, with explanations and how-to-fix hints
314
+ * lineage, tenant-isolation demonstrations and role permissions
315
+ * a benchmark dashboard that separates internal from independent results
316
+
317
+ ```bash
318
+ python -m pip install "sqlguard-ninja[demo]"
319
+ sqlninja demo serve # http://127.0.0.1:8000
320
+ ```
321
+
322
+ Allowed queries run in a restricted in-memory sandbox. The database itself enforces this
323
+ with an authorizer allowlist, engine limits and a time budget. A static build
324
+ (`scripts/build_static_demo.py`) runs the whole thing in the browser with Pyodide, with no
325
+ server. See [DEPLOYMENT.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DEPLOYMENT.md)
326
+ for Docker, Compose and Kubernetes.
327
+
303
328
  ## Observability and SARIF
304
329
 
305
330
  ```bash
@@ -396,7 +421,7 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
396
421
  ## Roadmap
397
422
 
398
423
  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:
424
+ held-out corpus (issue #6) and public hosting of the playground. Later:
400
425
  governed writes, and database-verified SQL Server and Oracle support. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
401
426
 
402
427
  ## Links
@@ -415,6 +440,7 @@ governed writes, and database-verified SQL Server and Oracle support. Details in
415
440
  * [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
416
441
  * [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
417
442
  * [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
443
+ * [Playground and deployment](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DEPLOYMENT.md)
418
444
  * [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
419
445
  * [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
420
446
  * [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
@@ -226,6 +226,26 @@ 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
+ ## Interactive playground
230
+
231
+ A web app on a synthetic retail database. It has:
232
+
233
+ * three enforceable roles (Sales Analyst, Customer Support, Store Manager)
234
+ * example queries, with explanations and how-to-fix hints
235
+ * lineage, tenant-isolation demonstrations and role permissions
236
+ * a benchmark dashboard that separates internal from independent results
237
+
238
+ ```bash
239
+ python -m pip install "sqlguard-ninja[demo]"
240
+ sqlninja demo serve # http://127.0.0.1:8000
241
+ ```
242
+
243
+ Allowed queries run in a restricted in-memory sandbox. The database itself enforces this
244
+ with an authorizer allowlist, engine limits and a time budget. A static build
245
+ (`scripts/build_static_demo.py`) runs the whole thing in the browser with Pyodide, with no
246
+ server. See [DEPLOYMENT.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DEPLOYMENT.md)
247
+ for Docker, Compose and Kubernetes.
248
+
229
249
  ## Observability and SARIF
230
250
 
231
251
  ```bash
@@ -322,7 +342,7 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
322
342
  ## Roadmap
323
343
 
324
344
  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:
345
+ held-out corpus (issue #6) and public hosting of the playground. Later:
326
346
  governed writes, and database-verified SQL Server and Oracle support. Details in [ROADMAP.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/ROADMAP.md).
327
347
 
328
348
  ## Links
@@ -341,6 +361,7 @@ governed writes, and database-verified SQL Server and Oracle support. Details in
341
361
  * [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
342
362
  * [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
343
363
  * [CI integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/CI_INTEGRATION.md)
364
+ * [Playground and deployment](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DEPLOYMENT.md)
344
365
  * [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
345
366
  * [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
346
367
  * [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
@@ -26,6 +26,7 @@ manager under their own licences. Licences were read from installed package meta
26
26
  | `mysql` | PyMySQL | 1.2.3 | MIT |
27
27
  | `otel` | opentelemetry-api | 1.45.1 | Apache-2.0 |
28
28
  | `prometheus` | prometheus-client | 0.26.0 | Apache-2.0 AND BSD-2-Clause |
29
+ | `demo` | fastapi / uvicorn | 0.162.0 / 0.54.0 | MIT / BSD-3-Clause |
29
30
 
30
31
  psycopg is LGPL-3.0. SQLGuard Ninja only imports it at runtime when the `postgres` extra is
31
32
  installed. It is not modified or bundled, which is compatible with distributing SQLGuard Ninja
@@ -38,7 +39,13 @@ mypy (MIT), build (MIT). `pgserver` is optionally used for local PostgreSQL test
38
39
  declared dependency, and its package metadata on PyPI does not declare a licence. It bundles
39
40
  PostgreSQL binaries (PostgreSQL License).
40
41
 
41
- Tests also use jsonschema (MIT) and opentelemetry-sdk (Apache-2.0).
42
+ Tests also use jsonschema (MIT), opentelemetry-sdk (Apache-2.0), Playwright and
43
+ pytest-playwright (Apache-2.0), and axe-core 4.10.3 (MPL-2.0), which is downloaded at test time.
44
+
45
+ The **static playground build** (`scripts/build_static_demo.py`, not part of the package)
46
+ bundles Pyodide 0.29.5 (MPL-2.0, including CPython under the PSF License and SQLite, which is
47
+ public domain), sqlglot (MIT), and pydantic, PyYAML and their dependencies as packaged by
48
+ Pyodide. Their licences are listed in the generated `NOTICE.txt`.
42
49
 
43
50
  ## Test fixture: SARIF 2.1.0 JSON schema
44
51
 
@@ -166,3 +166,16 @@ MySQL with server defaults, so the executor refuses sessions whose `sql_mode` co
166
166
  **Consequences.** This refines ADR-005: the executed string differs from the validated one only
167
167
  in placeholder nodes, which carry server-side values, never user input. Literals and identifiers
168
168
  are not rewritten. MySQL support is limited to the scope the CI service-container tests cover.
169
+
170
+ ## ADR-014 — Playground: one service, static-first hosting, database-enforced sandbox
171
+
172
+ **Decision.** One Python `DemoService` backs both a FastAPI server and a static build in which
173
+ Pyodide runs the same code in the visitor's browser. Allowed queries run only in an in-memory
174
+ synthetic SQLite database. That database enforces read-only access itself: an authorizer
175
+ allowlist, `query_only`, engine limits and a step/time budget. The public deployment is the
176
+ static build on GitHub Pages: there is no server to abuse, no credentials, and no cost. The
177
+ static build self-hosts Pyodide and the wheels and verifies their checksums.
178
+
179
+ **Consequences.** The first static load downloads about 16 MB. The server mode stays available
180
+ for private use behind a proxy (Docker/Compose/Kubernetes references). In-process rate limits
181
+ are per replica.
@@ -11,4 +11,6 @@
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
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) |
14
+ | 5 Observability: OTel spans, Prometheus metrics, audit schema, SARIF scan, Grafana + synthetic stack | RELEASED v0.6.0 | 3867989 | full gate 1,916 passed, 94%; CI stack: 224 decisions scraped, Grafana query success | redaction leak (hex/E''/$$/U&/N''/INTERVAL) found and fixed (T19) |
15
+ | 6 Playground: 3 roles, examples, explanations, lineage, tenant demo, permissions, benchmark dashboard; static Pyodide build | VERIFIED (local, CI pending) | see PR | 1,959 passed, 94%; 14 browser tests incl. axe + offline static | database-enforced sandbox |
16
+ | 7 Deployment: Dockerfile, Compose, Kubernetes, health, config validation, rate/body limits | VERIFIED (local tests; container in CI pending) | see PR | manifest and hardening tests | no Docker locally |
@@ -0,0 +1,5 @@
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.7.0 PR after CI (demo e2e + container jobs); add both jobs to required checks; release; verify PyPI + fresh `[demo]` install and `sqlninja demo serve`.
5
+ 3. Phase 8: public hosting needs maintainer authorization: enable GitHub Pages (Source: GitHub Actions), then run `pages.yml` and verify the real HTTPS URL in an unauthenticated browser.
@@ -19,3 +19,6 @@
19
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
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
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 |
22
+ | 2026-10-24 | v0.6.0 release | PR #24 14/14 (observability job: SARIF valid, decisions=224, 9 panels, grafana_query=success; 2nd run after tmpfs noexec fix); job added to required checks; main CI 40127541718 success; release.yml 40127737479 success | wheel + sdist on PyPI, both provenance 200; 0.1.0-0.5.0 unchanged |
23
+ | 2026-10-24 | v0.6.0 fresh install | `pip install --no-cache-dir "sqlguard-ninja[otel,prometheus]==0.6.0"` + opentelemetry-sdk, jsonschema (Python 3.13, outside repo, `-I`) | spans checkpoint+validate; decisions allow 2 / deny 1; canary in literal, hex, comment and context not leaked; redacted SQL correct; `scan` SARIF valid against OASIS schema fetched separately |
24
+ | 2026-10-24 | v0.7.0 candidate (playground + deployment) | full gate + `pytest -m e2e` (Playwright Chromium; static site built with verified downloads; external requests blocked) | 1,959 passed, 94%; 14 e2e passed incl. axe serious/critical = 0, 360/768 px no overflow; build + twine PASSED |
@@ -76,43 +76,50 @@
76
76
  {
77
77
  "id": 6,
78
78
  "name": "Observability and SARIF",
79
- "status": "VERIFIED",
79
+ "status": "RELEASED",
80
80
  "release": "v0.6.0",
81
81
  "issues": [
82
82
  14
83
83
  ],
84
84
  "evidence": [
85
- "OTel/Prometheus/SARIF/Grafana + canary privacy tests; 1,916 local tests; CI pending"
85
+ "OTel/Prometheus/SARIF/Grafana + canary privacy tests; 1,916 local tests; CI pending",
86
+ "v0.6.0 on PyPI with provenance; fresh [otel,prometheus] install verified"
86
87
  ],
87
88
  "blockers": []
88
89
  },
89
90
  {
90
91
  "id": 7,
91
92
  "name": "Web dashboard and playground",
92
- "status": "NOT_STARTED",
93
- "release": "v0.5.0",
93
+ "status": "VERIFIED",
94
+ "release": "v0.7.0",
94
95
  "issues": [],
95
- "evidence": [],
96
+ "evidence": [
97
+ "FastAPI + static Pyodide playground; 14 Playwright tests incl. axe; CI pending"
98
+ ],
96
99
  "blockers": []
97
100
  },
98
101
  {
99
102
  "id": 8,
100
103
  "name": "Production deployment",
101
- "status": "NOT_STARTED",
102
- "release": "v0.5.0",
104
+ "status": "VERIFIED",
105
+ "release": "v0.7.0",
103
106
  "issues": [],
104
- "evidence": [],
107
+ "evidence": [
108
+ "Dockerfile/Compose/Kubernetes with hardening tests; container CI pending"
109
+ ],
105
110
  "blockers": []
106
111
  },
107
112
  {
108
113
  "id": 9,
109
114
  "name": "Public zero-API-key demo",
110
- "status": "NOT_STARTED",
115
+ "status": "BLOCKED",
111
116
  "release": "v0.5.0",
112
117
  "issues": [],
113
- "evidence": [],
118
+ "evidence": [
119
+ "static build ready and tested offline; pages.yml manual workflow prepared"
120
+ ],
114
121
  "blockers": [
115
- "public hosting needs maintainer account authorization"
122
+ "needs maintainer authorization to enable GitHub Pages"
116
123
  ]
117
124
  }
118
125
  ]
@@ -0,0 +1,122 @@
1
+ # Playground and deployment
2
+
3
+ The playground is a web app on a synthetic retail database. It has:
4
+
5
+ * three roles: Sales Analyst, Customer Support and Store Manager
6
+ * example queries and plain-language explanations
7
+ * lineage, tenant isolation, role permissions and a benchmark dashboard
8
+
9
+ It needs no login, API key or credentials.
10
+
11
+ It runs in two modes from the same frontend and the same Python `DemoService`:
12
+
13
+ | Mode | How | Where SQL is validated and run |
14
+ |---|---|---|
15
+ | **Static** (recommended for public hosting) | `python scripts/build_static_demo.py site/` | in the visitor's browser, through Pyodide in a Web Worker; no server |
16
+ | **Server** | `pip install "sqlguard-ninja[demo]"` then `sqlninja demo serve` | FastAPI server, restricted in-memory sandbox |
17
+
18
+ ## Execution safety
19
+
20
+ The demo does not rely on the parser alone, or on rolling back a transaction. Only queries
21
+ the policy **allows** run, and they run against an in-memory SQLite database of synthetic
22
+ data. That database enforces its own restrictions (`sqlguard_ninja/demo/sandbox.py`):
23
+
24
+ * `PRAGMA query_only`.
25
+ * An **authorizer** that allows only `SELECT`, reads of the catalog's tables and an allowlist
26
+ of side-effect-free functions. ATTACH, PRAGMA, writes, schema changes, `sqlite_master` and
27
+ `load_extension` are refused by the engine.
28
+ * **Engine limits**: string length, SQL length, expression depth, compound selects, zero
29
+ attached databases, and few variables.
30
+ * A **step and time budget** through the progress handler. Recursive CTEs and cartesian
31
+ products are interrupted.
32
+ * A row cap of 100, plus value truncation.
33
+
34
+ The database holds no real data. In the static build it exists only in the visitor's browser.
35
+ Set `SQLNINJA_DEMO_EXECUTE=false` for a validation-only server.
36
+
37
+ `tests/unit/test_demo.py` checks every one of these refusals.
38
+
39
+ ## Server settings
40
+
41
+ Settings are read from the environment and validated at start-up. Unknown or invalid values
42
+ stop the server.
43
+
44
+ | Variable | Default | Meaning |
45
+ |---|---|---|
46
+ | `SQLNINJA_DEMO_EXECUTE` | `true` | run allowed queries in the sandbox |
47
+ | `SQLNINJA_DEMO_RATE_LIMIT_PER_MINUTE` | `60` | per-client API requests per minute (per process) |
48
+ | `SQLNINJA_DEMO_MAX_BODY_BYTES` | `16384` | request body cap; bodies without `Content-Length` are refused |
49
+ | `SQLNINJA_DEMO_TRUST_PROXY_HEADERS` | `false` | take the client from `X-Forwarded-For`; enable only behind a proxy that overwrites it |
50
+ | `SQLNINJA_DEMO_MAX_TRACKED_CLIENTS` | `10000` | memory bound for the rate limiter |
51
+
52
+ The server also:
53
+
54
+ * sends a strict CSP with no inline script, `frame-ancestors 'none'`, `nosniff`, and
55
+ `no-referrer`
56
+ * disables the OpenAPI and docs pages
57
+ * exposes `/healthz` (liveness) and `/readyz` (the sandbox answers)
58
+ * rejects identity forgery: role and tenant come from fixed enums, and extra fields are
59
+ rejected with 422
60
+
61
+ ## Docker and Compose
62
+
63
+ ```bash
64
+ docker build -t sqlguard-ninja-demo .
65
+ docker compose -f deploy/compose/docker-compose.yml up -d --build --wait
66
+ ```
67
+
68
+ The image is multi-stage, with the base pinned by digest. It runs as non-root (UID 10001) and
69
+ has a `HEALTHCHECK`. The Compose file adds:
70
+
71
+ * a read-only root filesystem with a small tmpfs
72
+ * `cap_drop: ALL` and `no-new-privileges`
73
+ * memory, CPU and PID limits
74
+ * port binding to localhost only
75
+
76
+ CI builds the image and runs it under those restrictions. It then checks that the user is
77
+ UID 10001, that the probes pass, that the CSP header is present, and that one allowed and one
78
+ denied API call behave correctly.
79
+
80
+ ## Kubernetes (example)
81
+
82
+ `deploy/kubernetes/` (Kustomize) contains:
83
+
84
+ * a Deployment:
85
+ * non-root with a RuntimeDefault seccomp profile
86
+ * read-only root filesystem with all capabilities dropped
87
+ * no service-account token
88
+ * resource limits
89
+ * startup, liveness and readiness probes
90
+ * a Service and a PodDisruptionBudget
91
+ * a NetworkPolicy that allows ingress only from the ingress controller and **no egress**
92
+ * an example Ingress with edge rate limits
93
+
94
+ CI validates these manifests against the Kubernetes schemas with `kubernetes-validate --strict`.
95
+ Replace the image reference with the digest you built and scanned. The in-process rate
96
+ limiter is per replica, so keep the edge limit.
97
+
98
+ ## Public hosting (decision)
99
+
100
+ Hosting is recorded as ADR-014. Several free options were compared.
101
+
102
+ **Static options:**
103
+
104
+ * **GitHub Pages** is free for public repositories and serves over HTTPS. It is already
105
+ part of the project's GitHub account, with no new vendor, and the build is a single
106
+ workflow.
107
+ * **Cloudflare Pages** and **Netlify** have free static tiers. Each would need a new
108
+ account and adds a vendor.
109
+
110
+ **Server options:** Render, Fly.io and Koyeb have free or trial container tiers. They add an
111
+ always-on attack surface, can sleep on idle, may change their terms, and some require a
112
+ payment card.
113
+
114
+ **Chosen: the static build on GitHub Pages.** Validation and execution happen in each
115
+ visitor's browser, so there is no shared server to abuse and no database to protect. It runs
116
+ at zero cost. The `_headers` file sets the CSP and related headers on hosts that support it.
117
+ On GitHub Pages, a `<meta>` CSP applies instead.
118
+
119
+ `.github/workflows/pages.yml` builds the site, tests that exact build in Chromium with every
120
+ external request blocked, and deploys it. It runs **only when started manually**, after the
121
+ maintainer enables Pages (Settings → Pages → Source: GitHub Actions). Do not describe the demo
122
+ as live until an unauthenticated browser has loaded the real HTTPS URL and run a query.
@@ -0,0 +1,22 @@
1
+ # SQLGuard Ninja v0.7.0
2
+
3
+ ## Highlights
4
+
5
+ * **Interactive playground.** Try SQL policy enforcement on a synthetic retail database with three roles: Sales Analyst, Customer Support and Store Manager. It has 20 example queries, explanations with fixes, lineage, tenant-isolation demonstrations, a role-permission browser, and a benchmark dashboard that labels every result as internal (1,070) and shows that no independent results exist (0). There is no login or API key: `pip install "sqlguard-ninja[demo]" && sqlninja demo serve`.
6
+ * **A sandbox the database enforces.** Allowed queries run in an in-memory synthetic SQLite database with an authorizer allowlist, `query_only`, engine limits and a time budget. Denied and review queries never run.
7
+ * **Runs in the browser.** `scripts/build_static_demo.py` builds a static site in which Pyodide runs the same code in a Web Worker. Everything is self-hosted and checksum-verified, and a browser test confirms the site makes no third-party requests.
8
+ * **Deployment references.** A hardened Dockerfile, Compose and Kubernetes manifests with health checks, validated configuration, rate limiting, body limits, query timeouts, non-root execution and resource limits. CI builds and runs the container under those restrictions and validates the manifests.
9
+
10
+ ## Notes
11
+
12
+ * The playground is not yet publicly hosted. A manual GitHub Pages workflow is ready; it needs the maintainer to enable Pages.
13
+ * All benchmark results remain internal (issues #5 and #6). SQL Server and Oracle remain parser-level experimental.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ python -m pip install "sqlguard-ninja[demo]==0.7.0"
19
+ sqlninja demo serve
20
+ ```
21
+
22
+ PyPI: https://pypi.org/project/sqlguard-ninja/0.7.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
+ | T20 | Abuse of the public playground | Resource exhaustion (recursive CTEs, cartesian joins, huge strings), sandbox escape attempts (ATTACH, PRAGMA, extensions, catalog reads), forged role or tenant in requests | Static build: everything runs in the visitor's own browser. Server: rate limit, body cap, enum identity, extra fields rejected; SQLite authorizer allowlist, `query_only`, engine limits, step/time budget, row cap; only ALLOW decisions execute | In-process limits are per replica; a determined client can still use its own CPU in static mode |
61
62
  | 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 |
62
63
  | 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 |
63
64
  | 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 |
@@ -43,7 +43,9 @@ mysql = ["PyMySQL>=1.1"]
43
43
  # Observability (sqlguard_ninja.observability).
44
44
  otel = ["opentelemetry-api>=1.25"]
45
45
  prometheus = ["prometheus-client>=0.20"]
46
- all = ["sqlguard-ninja[langchain,langgraph,fastapi,postgres,mysql,mcp,otel,prometheus]"]
46
+ # Interactive playground (sqlninja demo serve).
47
+ demo = ["fastapi>=0.110", "uvicorn>=0.30"]
48
+ all = ["sqlguard-ninja[langchain,langgraph,fastapi,postgres,mysql,mcp,otel,prometheus,demo]"]
47
49
  dev = [
48
50
  "sqlguard-ninja[all]",
49
51
  "pytest>=8.0",
@@ -88,6 +90,7 @@ markers = [
88
90
  "postgres: needs a PostgreSQL server (SQLNINJA_PG_DSN or pgserver)",
89
91
  "performance: latency measurements",
90
92
  "mysql: needs a MySQL server (SQLNINJA_MYSQL_DSN)",
93
+ "e2e: browser end-to-end tests (Playwright)",
91
94
  ]
92
95
  filterwarnings = ["error::DeprecationWarning:sqlguard_ninja.*"]
93
96
 
@@ -0,0 +1 @@
1
+ __version__ = "0.7.0"
@@ -365,6 +365,16 @@ def cmd_mcp(args: argparse.Namespace) -> int: # pragma: no cover - blocks on st
365
365
  return EXIT_OK
366
366
 
367
367
 
368
+ def cmd_demo_serve(args: argparse.Namespace) -> int: # pragma: no cover - blocks serving
369
+ try:
370
+ from sqlguard_ninja.demo.app import serve # noqa: PLC0415 - optional extra
371
+ except ImportError as exc:
372
+ raise CLIError('the demo needs the extra: pip install "sqlguard-ninja[demo]"') from exc
373
+ _out(f"SQLGuard Ninja demo on http://{args.host}:{args.port}/ (synthetic data only)")
374
+ serve(args.host, args.port)
375
+ return EXIT_OK
376
+
377
+
368
378
  def cmd_init(args: argparse.Namespace) -> int:
369
379
  root = Path(args.directory)
370
380
  existing = [name for name in FILES if (root / name).exists()]
@@ -579,6 +589,14 @@ def build_parser() -> argparse.ArgumentParser:
579
589
  sc.add_argument("--fail-on", choices=["deny", "review", "never"], default="deny")
580
590
  sc.set_defaults(func=cmd_scan)
581
591
 
592
+ dm = sub.add_parser("demo", help="interactive playground").add_subparsers(
593
+ dest="demo_cmd", required=True
594
+ )
595
+ dserve = dm.add_parser("serve", help="serve the playground (synthetic data, no login)")
596
+ dserve.add_argument("--host", default="127.0.0.1")
597
+ dserve.add_argument("--port", type=int, default=8000)
598
+ dserve.set_defaults(func=cmd_demo_serve)
599
+
582
600
  i = sub.add_parser("init", help="write a starter sqlninja.yaml, policy and test cases")
583
601
  i.add_argument("directory", nargs="?", default=".")
584
602
  i.add_argument("--force", action="store_true")