sqlguard-ninja 0.5.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.5.0 → sqlguard_ninja-0.7.0}/.gitignore +4 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/CHANGELOG.md +56 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/PKG-INFO +56 -4
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/README.md +40 -3
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/THIRD_PARTY_NOTICES.md +30 -1
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/architecture/ARCHITECTURE_DECISIONS.md +13 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/IMPLEMENTATION_PROGRESS.md +4 -1
- sqlguard_ninja-0.7.0/docs/development/NEXT_ACTIONS.md +5 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/TEST_EVIDENCE.md +6 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/progress.json +24 -14
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/CI_INTEGRATION.md +22 -0
- sqlguard_ninja-0.7.0/docs/guides/DEPLOYMENT.md +122 -0
- sqlguard_ninja-0.7.0/docs/guides/OBSERVABILITY.md +133 -0
- sqlguard_ninja-0.7.0/docs/releases/v0.6.0.md +24 -0
- sqlguard_ninja-0.7.0/docs/releases/v0.7.0.md +22 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/security/THREAT_MODEL.md +2 -0
- sqlguard_ninja-0.7.0/examples/observability/docker-compose.yml +47 -0
- sqlguard_ninja-0.7.0/examples/observability/grafana/provisioning/dashboards/sqlguard.yml +9 -0
- sqlguard_ninja-0.7.0/examples/observability/grafana/provisioning/datasources/prometheus.yml +9 -0
- sqlguard_ninja-0.7.0/examples/observability/prometheus.yml +6 -0
- sqlguard_ninja-0.7.0/examples/observability/smoke_check.py +42 -0
- sqlguard_ninja-0.7.0/examples/observability/synthetic_traffic.py +80 -0
- sqlguard_ninja-0.7.0/examples/sql/support_queries.sql +22 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/pyproject.toml +10 -2
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/_version.py +1 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/main.py +111 -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.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/audit.py +42 -5
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/checkpoint.py +30 -2
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/observability/__init__.py +17 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/observability/metrics.py +121 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/observability/telemetry.py +148 -0
- sqlguard_ninja-0.7.0/src/sqlguard_ninja/reporting/sarif.py +195 -0
- sqlguard_ninja-0.7.0/tests/data/sarif-schema-2.1.0.json +3389 -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.7.0/tests/unit/test_grafana_dashboard.py +84 -0
- sqlguard_ninja-0.7.0/tests/unit/test_observability.py +381 -0
- sqlguard_ninja-0.5.0/docs/development/NEXT_ACTIONS.md +0 -6
- sqlguard_ninja-0.5.0/src/sqlguard_ninja/_version.py +0 -1
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/LICENSE +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/baselines/native.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/external/README.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/LABEL_CHANGES.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/01_destructive.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/04_privacy.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/08_lineage.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/cases/09_mysql.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/grant_matrix.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/label_matrix.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/tenant_matrix.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/generated/transforms.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/datasets/v1/manifest.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/policies/retail_saas.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/native.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.0.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/native.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.1.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/agent-sql-guard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/krish2105-sqlguard.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/native.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.csv +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.json +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/benchmarks/results/v1.2.0/sql-guardrail.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.1.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.2.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/benchmarks/RESULTS_v1.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/ARCHITECTURE_MAP.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/DECISION_LOG.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/development/UPGRADE_MASTER_PLAN.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/AGENT_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_BIAS_REVIEW.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/BENCHMARK_METHODOLOGY.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/DATABASE_COMPATIBILITY.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/MCP_INTEGRATION.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_AS_CODE.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/POLICY_REFERENCE.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/guides/QUERY_COST.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/release/FINAL_TECHNICAL_AUDIT.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.0.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.1.1.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.2.0.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.3.0.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.4.0.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/releases/v0.5.0.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/research/COMPETITIVE_ANALYSIS.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/docs/security/STATIC_ANALYSIS_LIMITS.md +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/_shared.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/fastapi/app.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/github-actions/sql-security.yml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/langchain/guarded_tool_demo.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/langgraph/real_llm_agent.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/langgraph/sql_agent_demo.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/postgresql/plan_inspection_demo.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/sdk/quickstart.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/examples/sqlite/text_to_sql_demo.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/adapters.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/builtin.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/compare.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/dataset.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/fixtures.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/metrics.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/runner.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/translate.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/_protocol.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/config.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/cli/templates.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/analysis.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/findings.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/guard.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/lineage.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/access.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/base.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/catalog.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/functions.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/statements.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/structure.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/core/rules/tenant.py +0 -0
- {sqlguard_ninja-0.5.0/tests → sqlguard_ninja-0.7.0/src/sqlguard_ninja/demo}/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/executors.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/fastapi.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langchain.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/langgraph.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/integrations/mysql.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/mcp_server.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/inheritance.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/model.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/analytics_readonly.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/minimal.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/templates/multi_tenant_saas.yaml +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/policy/tools.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/rules.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/privacy/suggest.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/py.typed +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/postgres.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/query_cost/static.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/reporting/markdown.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/src/sqlguard_ninja/tenancy.py +0 -0
- {sqlguard_ninja-0.5.0/tests/integration → sqlguard_ninja-0.7.0/tests}/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/conftest.py +0 -0
- {sqlguard_ninja-0.5.0/tests/property → sqlguard_ninja-0.7.0/tests/integration}/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_corpus_sqlite_validation.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_external_adapters.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_frameworks.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mcp_server.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_mysql.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/integration/test_postgres.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/pg_support.py +0 -0
- {sqlguard_ninja-0.5.0/tests/security → sqlguard_ninja-0.7.0/tests/property}/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/property/test_guard_properties.py +0 -0
- {sqlguard_ninja-0.5.0/tests/unit → sqlguard_ninja-0.7.0/tests/security}/__init__.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/security/test_agent_boundaries.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/security/test_corpus_native.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/security/test_tenant_rewrite.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/helpers.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_benchmark_engine.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_cli.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_dataset.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_docs.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_experimental_dialects.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_function_policy_mechanics.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_lineage.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_mysql_binding.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_inheritance.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_policy_tools.py +0 -0
- {sqlguard_ninja-0.5.0 → sqlguard_ninja-0.7.0}/tests/unit/test_rules.py +0 -0
|
@@ -3,6 +3,62 @@
|
|
|
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
|
+
|
|
39
|
+
## [0.6.0] — 2026-10-24
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
* **Observability** (`sqlguard_ninja.observability`, extras `[otel]` and `[prometheus]`):
|
|
43
|
+
* `Telemetry` and `InstrumentedGuard` emit OpenTelemetry spans `sqlguard.checkpoint` and
|
|
44
|
+
`sqlguard.validate`.
|
|
45
|
+
* `GuardMetrics` exports Prometheus counters and histograms for decisions, findings,
|
|
46
|
+
latency, errors, resource-limit rejections, checkpoint outcomes and policy identity.
|
|
47
|
+
* `SQLCheckpoint(telemetry=...)` instruments its guard automatically.
|
|
48
|
+
* **SARIF 2.1.0**: `sqlninja scan PATH... --format sarif|json|text --fail-on deny|review|never`.
|
|
49
|
+
It validates every statement in SQL files, with stable fingerprints. Tests and CI validate the
|
|
50
|
+
output against the OASIS schema.
|
|
51
|
+
* **Grafana dashboard** `deploy/grafana/sqlguard-ninja.json`, and a Prometheus + Grafana +
|
|
52
|
+
synthetic-traffic stack (`examples/observability/`). A new CI job runs that stack.
|
|
53
|
+
* Audit events gain `schema_version` and `rate_limited`. New
|
|
54
|
+
`SQLCheckpoint(principal_hmac_key=...)` option to pseudonymise principals.
|
|
55
|
+
|
|
56
|
+
### Security
|
|
57
|
+
* `redact_sql` now redacts hex, bit, byte (`E''`), dollar-quoted, Unicode (`U&''`) and
|
|
58
|
+
national (`N''`) strings and whole `INTERVAL` expressions. Previously these could reach
|
|
59
|
+
`sql_redacted` in audit logs. Comments are dropped, and if any literal token survives,
|
|
60
|
+
redacted SQL is withheld. See threat model T19.
|
|
61
|
+
|
|
6
62
|
## [0.5.0] — 2026-10-24
|
|
7
63
|
|
|
8
64
|
### 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
|
|
@@ -30,23 +30,34 @@ Requires-Dist: fastapi>=0.110; extra == 'all'
|
|
|
30
30
|
Requires-Dist: langchain-core>=0.3; extra == 'all'
|
|
31
31
|
Requires-Dist: langgraph>=0.2; extra == 'all'
|
|
32
32
|
Requires-Dist: mcp<3,>=2.3; extra == 'all'
|
|
33
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == 'all'
|
|
34
|
+
Requires-Dist: prometheus-client>=0.20; extra == 'all'
|
|
33
35
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
|
|
34
36
|
Requires-Dist: pymysql>=1.1; extra == 'all'
|
|
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'
|
|
35
41
|
Provides-Extra: dev
|
|
36
42
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
37
43
|
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
38
44
|
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
39
45
|
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
46
|
+
Requires-Dist: jsonschema>=4.20; extra == 'dev'
|
|
40
47
|
Requires-Dist: langchain-core>=0.3; extra == 'dev'
|
|
41
48
|
Requires-Dist: langgraph>=0.2; extra == 'dev'
|
|
42
49
|
Requires-Dist: mcp<3,>=2.3; extra == 'dev'
|
|
43
50
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
51
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == 'dev'
|
|
52
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'dev'
|
|
53
|
+
Requires-Dist: prometheus-client>=0.20; extra == 'dev'
|
|
44
54
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'dev'
|
|
45
55
|
Requires-Dist: pymysql>=1.1; extra == 'dev'
|
|
46
56
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
47
57
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
48
58
|
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
49
59
|
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
60
|
+
Requires-Dist: uvicorn>=0.30; extra == 'dev'
|
|
50
61
|
Provides-Extra: fastapi
|
|
51
62
|
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
52
63
|
Provides-Extra: langchain
|
|
@@ -58,8 +69,12 @@ Provides-Extra: mcp
|
|
|
58
69
|
Requires-Dist: mcp<3,>=2.3; extra == 'mcp'
|
|
59
70
|
Provides-Extra: mysql
|
|
60
71
|
Requires-Dist: pymysql>=1.1; extra == 'mysql'
|
|
72
|
+
Provides-Extra: otel
|
|
73
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == 'otel'
|
|
61
74
|
Provides-Extra: postgres
|
|
62
75
|
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
76
|
+
Provides-Extra: prometheus
|
|
77
|
+
Requires-Dist: prometheus-client>=0.20; extra == 'prometheus'
|
|
63
78
|
Description-Content-Type: text/markdown
|
|
64
79
|
|
|
65
80
|
# SQLGuard Ninja
|
|
@@ -290,6 +305,40 @@ The four tools are `validate_sql`, `explain_policy_decision`, `inspect_query_cos
|
|
|
290
305
|
to a database, and it exposes no execution tool. Tested with the official MCP Python SDK
|
|
291
306
|
client over stdio. See [MCP_INTEGRATION.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/MCP_INTEGRATION.md).
|
|
292
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
|
+
|
|
328
|
+
## Observability and SARIF
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
python -m pip install "sqlguard-ninja[otel,prometheus]"
|
|
332
|
+
sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`Telemetry(tracer=..., metrics=GuardMetrics())` adds OpenTelemetry spans and Prometheus metrics
|
|
336
|
+
to the checkpoint: decisions, findings by rule, latency, errors, resource-limit rejections and
|
|
337
|
+
checkpoint outcomes. By default no signal contains SQL text, literals, request-context values,
|
|
338
|
+
tenant ids or principals, and tests check this with canary values. A Grafana dashboard
|
|
339
|
+
(`deploy/grafana/`) and a synthetic-traffic Prometheus/Grafana stack are included. SARIF output
|
|
340
|
+
validates against the OASIS 2.1.0 schema. See [OBSERVABILITY.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md).
|
|
341
|
+
|
|
293
342
|
## Query cost analysis
|
|
294
343
|
|
|
295
344
|
Every validation includes an explainable static score that uses the catalog's
|
|
@@ -371,9 +420,9 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
|
|
|
371
420
|
|
|
372
421
|
## Roadmap
|
|
373
422
|
|
|
374
|
-
Next: restricted-function and system-catalog benchmark cases, an independent
|
|
375
|
-
corpus
|
|
376
|
-
writes,
|
|
423
|
+
Next: restricted-function and system-catalog benchmark cases (issue #5), an independent
|
|
424
|
+
held-out corpus (issue #6) and public hosting of the playground. Later:
|
|
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).
|
|
377
426
|
|
|
378
427
|
## Links
|
|
379
428
|
|
|
@@ -391,6 +440,9 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
|
|
|
391
440
|
* [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
|
|
392
441
|
* [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
|
|
393
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)
|
|
444
|
+
* [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
|
|
445
|
+
* [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
|
|
394
446
|
* [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
|
|
395
447
|
* [Threat model](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/THREAT_MODEL.md)
|
|
396
448
|
* [Limits of static SQL analysis and dialect support](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/STATIC_ANALYSIS_LIMITS.md)
|
|
@@ -226,6 +226,40 @@ 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
|
+
|
|
249
|
+
## Observability and SARIF
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
python -m pip install "sqlguard-ninja[otel,prometheus]"
|
|
253
|
+
sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`Telemetry(tracer=..., metrics=GuardMetrics())` adds OpenTelemetry spans and Prometheus metrics
|
|
257
|
+
to the checkpoint: decisions, findings by rule, latency, errors, resource-limit rejections and
|
|
258
|
+
checkpoint outcomes. By default no signal contains SQL text, literals, request-context values,
|
|
259
|
+
tenant ids or principals, and tests check this with canary values. A Grafana dashboard
|
|
260
|
+
(`deploy/grafana/`) and a synthetic-traffic Prometheus/Grafana stack are included. SARIF output
|
|
261
|
+
validates against the OASIS 2.1.0 schema. See [OBSERVABILITY.md](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md).
|
|
262
|
+
|
|
229
263
|
## Query cost analysis
|
|
230
264
|
|
|
231
265
|
Every validation includes an explainable static score that uses the catalog's
|
|
@@ -307,9 +341,9 @@ the [benchmark bias review](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/m
|
|
|
307
341
|
|
|
308
342
|
## Roadmap
|
|
309
343
|
|
|
310
|
-
Next: restricted-function and system-catalog benchmark cases, an independent
|
|
311
|
-
corpus
|
|
312
|
-
writes,
|
|
344
|
+
Next: restricted-function and system-catalog benchmark cases (issue #5), an independent
|
|
345
|
+
held-out corpus (issue #6) and public hosting of the playground. Later:
|
|
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).
|
|
313
347
|
|
|
314
348
|
## Links
|
|
315
349
|
|
|
@@ -327,6 +361,9 @@ writes, more tested dialects, and SARIF output. Details in [ROADMAP.md](https://
|
|
|
327
361
|
* [Agent integration](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/AGENT_INTEGRATION.md)
|
|
328
362
|
* [Query cost](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/QUERY_COST.md)
|
|
329
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)
|
|
365
|
+
* [Observability and SARIF](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/OBSERVABILITY.md)
|
|
366
|
+
* [Database compatibility](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/DATABASE_COMPATIBILITY.md)
|
|
330
367
|
* [Benchmark methodology](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/guides/BENCHMARK_METHODOLOGY.md)
|
|
331
368
|
* [Threat model](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/THREAT_MODEL.md)
|
|
332
369
|
* [Limits of static SQL analysis and dialect support](https://github.com/Pratik653/SQLGuard_Ninjapy/blob/main/docs/security/STATIC_ANALYSIS_LIMITS.md)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
3
|
SQLGuard Ninja is licensed under Apache-2.0. **It does not vendor or copy third-party source
|
|
4
|
-
code.** The
|
|
4
|
+
code.** The only third-party file in the repository is a test fixture, the SARIF schema
|
|
5
|
+
(below). The projects below are used as ordinary dependencies, installed by the package
|
|
5
6
|
manager under their own licences. Licences were read from installed package metadata on
|
|
6
7
|
2026-10-24. Always check the version you install.
|
|
7
8
|
|
|
@@ -21,6 +22,11 @@ manager under their own licences. Licences were read from installed package meta
|
|
|
21
22
|
| `langgraph` | langgraph | 1.2.17 | MIT |
|
|
22
23
|
| `fastapi` | fastapi / starlette | 0.162.0 / 1.9.0 | MIT / BSD-3-Clause |
|
|
23
24
|
| `postgres` | psycopg (+ psycopg-binary) | 3.3.6 | LGPL-3.0-only |
|
|
25
|
+
| `mcp` | mcp (official MCP Python SDK) | 2.3.0 | MIT |
|
|
26
|
+
| `mysql` | PyMySQL | 1.2.3 | MIT |
|
|
27
|
+
| `otel` | opentelemetry-api | 1.45.1 | Apache-2.0 |
|
|
28
|
+
| `prometheus` | prometheus-client | 0.26.0 | Apache-2.0 AND BSD-2-Clause |
|
|
29
|
+
| `demo` | fastapi / uvicorn | 0.162.0 / 0.54.0 | MIT / BSD-3-Clause |
|
|
24
30
|
|
|
25
31
|
psycopg is LGPL-3.0. SQLGuard Ninja only imports it at runtime when the `postgres` extra is
|
|
26
32
|
installed. It is not modified or bundled, which is compatible with distributing SQLGuard Ninja
|
|
@@ -33,6 +39,29 @@ mypy (MIT), build (MIT). `pgserver` is optionally used for local PostgreSQL test
|
|
|
33
39
|
declared dependency, and its package metadata on PyPI does not declare a licence. It bundles
|
|
34
40
|
PostgreSQL binaries (PostgreSQL License).
|
|
35
41
|
|
|
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`.
|
|
49
|
+
|
|
50
|
+
## Test fixture: SARIF 2.1.0 JSON schema
|
|
51
|
+
|
|
52
|
+
`tests/data/sarif-schema-2.1.0.json` is the unmodified OASIS SARIF 2.1.0 (Errata 01) schema,
|
|
53
|
+
downloaded from
|
|
54
|
+
https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json
|
|
55
|
+
(SHA-256 `c3b4bb2d6093897483348925aaa73af03b3e3f4bd4ca38cef26dcb4212a2682e`). It is used only to
|
|
56
|
+
validate SARIF output in tests and is not part of the wheel.
|
|
57
|
+
|
|
58
|
+
> Copyright © OASIS Open 2020. All Rights Reserved. This document and translations of it may be
|
|
59
|
+
> copied and furnished to others, and derivative works that comment on or otherwise explain it
|
|
60
|
+
> or assist in its implementation may be prepared, copied, published, and distributed, in whole
|
|
61
|
+
> or in part, without restriction of any kind, provided that the above copyright notice and this
|
|
62
|
+
> section are included on all such copies and derivative works. (OASIS IPR Policy notice; see
|
|
63
|
+
> https://www.oasis-open.org/policies-guidelines/ipr/)
|
|
64
|
+
|
|
36
65
|
## External validators used for benchmarking only
|
|
37
66
|
|
|
38
67
|
Installed into isolated virtual environments by `scripts/setup_external_adapters.py`, never
|
|
@@ -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.
|
|
@@ -10,4 +10,7 @@
|
|
|
10
10
|
| 2c Opt-in AST tenant-filter rewrite + checkpoint | VERIFIED (local) | b0b5ade | test_tenant_rewrite (32, executes on SQLite, tenants 1-3) | |
|
|
11
11
|
| 2d Docs, ADR-011/012, v0.3.0 bump | RELEASED v0.3.0 | 198a308 | full gate 1,804 passed, 94% | |
|
|
12
12
|
| 3 MCP server (stdio, validation-only) | RELEASED v0.4.0 | ecec4da | 9 tests incl. real SDK client; published-package stdio smoke test | version targets adapted |
|
|
13
|
-
| 4 MySQL tested dialect; SQL Server/Oracle experimental; compatibility matrix |
|
|
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 | 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.
|
|
@@ -16,3 +16,9 @@
|
|
|
16
16
|
| 2026-10-24 | v0.4.0 release | PR #20 12/12; main CI on ecec4da success; release.yml run 40117676554 success | wheel and sdist on PyPI, both provenance endpoints 200; 0.1.0-0.3.0 unchanged |
|
|
17
17
|
| 2026-10-24 | v0.4.0 fresh install + MCP stdio | `pip install --no-cache-dir "sqlguard-ninja[mcp]==0.4.0"` (Python 3.13, outside repo, 4th attempt; CDN lag), then SDK `stdio_client`/`ClientSession` against installed `sqlninja mcp` | 4 tools; allow/deny/cross-tenant deny; 100,001-char input `invalid_input`; forged `role` argument has no effect (deny); capabilities hide denied tables, `read_only: true`; no finding detail leaked |
|
|
18
18
|
| 2026-10-24 | v0.5.0 candidate (MySQL) | full gate (`ruff`, `mypy`, corpus `--check`, `pytest --cov -m "not mysql"` incl. embedded PostgreSQL, build, `twine check --strict`) | 1,881 passed, 94%; twine PASSED; corpus 1.2.0: 1,070 evaluations, 4 adapters regenerated |
|
|
19
|
+
| 2026-10-24 | v0.5.0 release | PR #22 13/13 (MySQL job: 15 passed, 0 skipped); MySQL job added to required checks; main CI 40119298660 on de4d03f success; release.yml 40119480081 success | wheel and sdist on PyPI, both provenance endpoints 200; 0.1.0-0.4.0 unchanged |
|
|
20
|
+
| 2026-10-24 | v0.5.0 fresh install | `pip install --no-cache-dir "sqlguard-ninja[mysql]==0.5.0"` (Python 3.13, outside repo, `-I`; 2nd attempt) | PyMySQL 1.2.3; dialects include mysql; bundled corpus 1.2.0, 26 MySQL cases 0 mismatches; binding and sql_mode refusals; `o.ID` and card column denied. No MySQL server locally, so execution is evidenced by CI only |
|
|
21
|
+
| 2026-10-24 | v0.6.0 candidate (observability) | full gate (`ruff`, `mypy`, corpus `--check`, `pytest --cov -m "not mysql"`, build, `twine check --strict`) | 1,916 passed, 94%; twine PASSED; SARIF schema-valid; canary privacy tests over 13 literal forms |
|
|
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 |
|
|
@@ -62,54 +62,64 @@
|
|
|
62
62
|
{
|
|
63
63
|
"id": 5,
|
|
64
64
|
"name": "Database dialects",
|
|
65
|
-
"status": "
|
|
65
|
+
"status": "RELEASED",
|
|
66
66
|
"release": "v0.5.0",
|
|
67
67
|
"issues": [
|
|
68
68
|
11
|
|
69
69
|
],
|
|
70
70
|
"evidence": [
|
|
71
|
-
"MySQL executor/binding/sql_mode guard, 26 corpus cases; 1,881 local tests; CI MySQL job pending"
|
|
71
|
+
"MySQL executor/binding/sql_mode guard, 26 corpus cases; 1,881 local tests; CI MySQL job pending",
|
|
72
|
+
"v0.5.0 on PyPI with provenance; CI MySQL 8.4 15/15; fresh [mysql] install verified"
|
|
72
73
|
],
|
|
73
74
|
"blockers": []
|
|
74
75
|
},
|
|
75
76
|
{
|
|
76
77
|
"id": 6,
|
|
77
78
|
"name": "Observability and SARIF",
|
|
78
|
-
"status": "
|
|
79
|
-
"release": "v0.
|
|
79
|
+
"status": "RELEASED",
|
|
80
|
+
"release": "v0.6.0",
|
|
80
81
|
"issues": [
|
|
81
82
|
14
|
|
82
83
|
],
|
|
83
|
-
"evidence": [
|
|
84
|
+
"evidence": [
|
|
85
|
+
"OTel/Prometheus/SARIF/Grafana + canary privacy tests; 1,916 local tests; CI pending",
|
|
86
|
+
"v0.6.0 on PyPI with provenance; fresh [otel,prometheus] install verified"
|
|
87
|
+
],
|
|
84
88
|
"blockers": []
|
|
85
89
|
},
|
|
86
90
|
{
|
|
87
91
|
"id": 7,
|
|
88
92
|
"name": "Web dashboard and playground",
|
|
89
|
-
"status": "
|
|
90
|
-
"release": "v0.
|
|
93
|
+
"status": "VERIFIED",
|
|
94
|
+
"release": "v0.7.0",
|
|
91
95
|
"issues": [],
|
|
92
|
-
"evidence": [
|
|
96
|
+
"evidence": [
|
|
97
|
+
"FastAPI + static Pyodide playground; 14 Playwright tests incl. axe; CI pending"
|
|
98
|
+
],
|
|
93
99
|
"blockers": []
|
|
94
100
|
},
|
|
95
101
|
{
|
|
96
102
|
"id": 8,
|
|
97
103
|
"name": "Production deployment",
|
|
98
|
-
"status": "
|
|
99
|
-
"release": "v0.
|
|
104
|
+
"status": "VERIFIED",
|
|
105
|
+
"release": "v0.7.0",
|
|
100
106
|
"issues": [],
|
|
101
|
-
"evidence": [
|
|
107
|
+
"evidence": [
|
|
108
|
+
"Dockerfile/Compose/Kubernetes with hardening tests; container CI pending"
|
|
109
|
+
],
|
|
102
110
|
"blockers": []
|
|
103
111
|
},
|
|
104
112
|
{
|
|
105
113
|
"id": 9,
|
|
106
114
|
"name": "Public zero-API-key demo",
|
|
107
|
-
"status": "
|
|
115
|
+
"status": "BLOCKED",
|
|
108
116
|
"release": "v0.5.0",
|
|
109
117
|
"issues": [],
|
|
110
|
-
"evidence": [
|
|
118
|
+
"evidence": [
|
|
119
|
+
"static build ready and tested offline; pages.yml manual workflow prepared"
|
|
120
|
+
],
|
|
111
121
|
"blockers": [
|
|
112
|
-
"
|
|
122
|
+
"needs maintainer authorization to enable GitHub Pages"
|
|
113
123
|
]
|
|
114
124
|
}
|
|
115
125
|
]
|
|
@@ -11,6 +11,28 @@ git add benchmarks/baseline.json # baselines change only through reviewed commi
|
|
|
11
11
|
|
|
12
12
|
`sqlninja benchmark baseline` refuses to run when `CI` is set. CI never updates baselines.
|
|
13
13
|
|
|
14
|
+
## SARIF and GitHub code scanning
|
|
15
|
+
|
|
16
|
+
`sqlninja scan` writes SARIF 2.1.0. Upload it with GitHub's own action, pinned to a commit SHA
|
|
17
|
+
you have reviewed. Uploading needs `security-events: write`, and code scanning must be available
|
|
18
|
+
for the repository.
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
security-events: write
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@<sha>
|
|
26
|
+
- run: pip install sqlguard-ninja
|
|
27
|
+
- run: sqlninja scan sql/ --policy policy.yaml --role reporting --tenant 1 --format sarif --output sqlninja.sarif --fail-on never
|
|
28
|
+
- uses: github/codeql-action/upload-sarif@<sha>
|
|
29
|
+
with:
|
|
30
|
+
sarif_file: sqlninja.sarif
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Results are fingerprinted per file, rule and literal-insensitive query, so changing only a
|
|
34
|
+
literal does not open a new alert. The log contains rule messages but no SQL snippet.
|
|
35
|
+
|
|
14
36
|
## GitHub Action
|
|
15
37
|
|
|
16
38
|
See [`examples/github-actions/sql-security.yml`](../../examples/github-actions/sql-security.yml).
|
|
@@ -0,0 +1,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.
|