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.
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/.gitignore +4 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/CHANGELOG.md +33 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/PKG-INFO +28 -2
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/README.md +22 -1
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/THIRD_PARTY_NOTICES.md +8 -1
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +13 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/IMPLEMENTATION_PROGRESS.md +3 -1
- sqlguard_ninja-0.7.0/docs/development/NEXT_ACTIONS.md +5 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/TEST_EVIDENCE.md +3 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/progress.json +18 -11
- sqlguard_ninja-0.7.0/docs/guides/DEPLOYMENT.md +122 -0
- sqlguard_ninja-0.7.0/docs/releases/v0.7.0.md +22 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/security/THREAT_MODEL.md +1 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/pyproject.toml +4 -1
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/_version.py +1 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/main.py +18 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/app.py +195 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/examples.py +222 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/roles.yaml +55 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/sandbox.py +146 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/service.py +316 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/app.js +490 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/data/benchmark.json +573 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/favicon.svg +1 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/index.html +147 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/styles.css +220 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo/static/worker.js +38 -0
- sqlguard_ninja-0.7.0/tests/e2e/conftest.py +84 -0
- sqlguard_ninja-0.7.0/tests/e2e/test_demo_e2e.py +165 -0
- sqlguard_ninja-0.7.0/tests/e2e/test_static_demo_e2e.py +80 -0
- sqlguard_ninja-0.7.0/tests/unit/__init__.py +0 -0
- sqlguard_ninja-0.7.0/tests/unit/test_demo.py +274 -0
- sqlguard_ninja-0.6.0/docs/development/NEXT_ACTIONS.md +0 -6
- sqlguard_ninja-0.6.0/src/sqlguard_ninja/_version.py +0 -1
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/LICENSE +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/baselines/native.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/external/README.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/09_mysql.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/policies/retail_saas.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.csv +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.2.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/DECISION_LOG.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/AGENT_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/CI_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/DATABASE_COMPATIBILITY.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/MCP_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/OBSERVABILITY.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_AS_CODE.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_REFERENCE.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/guides/QUERY_COST.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.1.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.2.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.3.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.4.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.5.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.6.0.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/_shared.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/fastapi/app.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/github-actions/sql-security.yml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langchain/guarded_tool_demo.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langgraph/real_llm_agent.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/langgraph/sql_agent_demo.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/docker-compose.yml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/grafana/provisioning/dashboards/sqlguard.yml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/grafana/provisioning/datasources/prometheus.yml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/prometheus.yml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/smoke_check.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/observability/synthetic_traffic.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/postgresql/plan_inspection_demo.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sdk/quickstart.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sql/support_queries.sql +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/examples/sqlite/text_to_sql_demo.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/config.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/templates.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/analysis.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/findings.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/guard.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/lineage.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
- {sqlguard_ninja-0.6.0/tests → sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo}/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/audit.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/checkpoint.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/mysql.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/mcp_server.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/metrics.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/observability/telemetry.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/inheritance.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/model.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/minimal.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/tools.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/py.typed +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/sarif.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/tenancy.py +0 -0
- {sqlguard_ninja-0.6.0/tests/integration → sqlguard_ninja-0.7.0/tests}/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/conftest.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/data/sarif-schema-2.1.0.json +0 -0
- {sqlguard_ninja-0.6.0/tests/property → sqlguard_ninja-0.7.0/tests/integration}/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_external_adapters.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_frameworks.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mcp_server.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mysql.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/integration/test_postgres.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/pg_support.py +0 -0
- {sqlguard_ninja-0.6.0/tests/security → sqlguard_ninja-0.7.0/tests/property}/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/property/test_guard_properties.py +0 -0
- {sqlguard_ninja-0.6.0/tests/unit → sqlguard_ninja-0.7.0/tests/security}/__init__.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_agent_boundaries.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_corpus_native.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/security/test_tenant_rewrite.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/helpers.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_benchmark_engine.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_cli.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_dataset.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_docs.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_experimental_dialects.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_function_policy_mechanics.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_grafana_dashboard.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_lineage.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_mysql_binding.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_observability.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_inheritance.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_tools.py +0 -0
- {sqlguard_ninja-0.6.0 → sqlguard_ninja-0.7.0}/tests/unit/test_rules.py +0 -0
|
@@ -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.
|
|
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)
|
|
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)
|
|
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)
|
|
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 |
|
|
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": "
|
|
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": "
|
|
93
|
-
"release": "v0.
|
|
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": "
|
|
102
|
-
"release": "v0.
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
|
|
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")
|