sqlguard-ninja 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. sqlguard_ninja/__init__.py +35 -0
  2. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/01_destructive.yaml +169 -0
  3. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +155 -0
  4. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +205 -0
  5. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/04_privacy.yaml +245 -0
  6. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +167 -0
  7. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +168 -0
  8. sqlguard_ninja/_data/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +243 -0
  9. sqlguard_ninja/_data/benchmarks/datasets/v1/generated/grant_matrix.yaml +573 -0
  10. sqlguard_ninja/_data/benchmarks/datasets/v1/generated/label_matrix.yaml +403 -0
  11. sqlguard_ninja/_data/benchmarks/datasets/v1/generated/tenant_matrix.yaml +684 -0
  12. sqlguard_ninja/_data/benchmarks/datasets/v1/generated/transforms.yaml +927 -0
  13. sqlguard_ninja/_data/benchmarks/datasets/v1/manifest.yaml +15 -0
  14. sqlguard_ninja/_data/benchmarks/policies/retail_saas.yaml +259 -0
  15. sqlguard_ninja/_version.py +1 -0
  16. sqlguard_ninja/benchmarking/__init__.py +1 -0
  17. sqlguard_ninja/benchmarking/adapters.py +303 -0
  18. sqlguard_ninja/benchmarking/builtin.py +39 -0
  19. sqlguard_ninja/benchmarking/compare.py +168 -0
  20. sqlguard_ninja/benchmarking/dataset.py +265 -0
  21. sqlguard_ninja/benchmarking/fixtures.py +274 -0
  22. sqlguard_ninja/benchmarking/metrics.py +114 -0
  23. sqlguard_ninja/benchmarking/runner.py +126 -0
  24. sqlguard_ninja/benchmarking/translate.py +64 -0
  25. sqlguard_ninja/benchmarking/workers/_protocol.py +53 -0
  26. sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +40 -0
  27. sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +25 -0
  28. sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +48 -0
  29. sqlguard_ninja/cli/__init__.py +1 -0
  30. sqlguard_ninja/cli/config.py +76 -0
  31. sqlguard_ninja/cli/main.py +433 -0
  32. sqlguard_ninja/cli/templates.py +91 -0
  33. sqlguard_ninja/core/__init__.py +1 -0
  34. sqlguard_ninja/core/analysis.py +581 -0
  35. sqlguard_ninja/core/findings.py +133 -0
  36. sqlguard_ninja/core/guard.py +256 -0
  37. sqlguard_ninja/core/rules/__init__.py +1 -0
  38. sqlguard_ninja/core/rules/access.py +127 -0
  39. sqlguard_ninja/core/rules/base.py +40 -0
  40. sqlguard_ninja/core/rules/catalog.py +61 -0
  41. sqlguard_ninja/core/rules/functions.py +197 -0
  42. sqlguard_ninja/core/rules/statements.py +99 -0
  43. sqlguard_ninja/core/rules/structure.py +72 -0
  44. sqlguard_ninja/core/rules/tenant.py +196 -0
  45. sqlguard_ninja/integrations/__init__.py +43 -0
  46. sqlguard_ninja/integrations/audit.py +109 -0
  47. sqlguard_ninja/integrations/checkpoint.py +342 -0
  48. sqlguard_ninja/integrations/executors.py +64 -0
  49. sqlguard_ninja/integrations/fastapi.py +89 -0
  50. sqlguard_ninja/integrations/langchain.py +67 -0
  51. sqlguard_ninja/integrations/langgraph.py +87 -0
  52. sqlguard_ninja/policy/__init__.py +39 -0
  53. sqlguard_ninja/policy/model.py +370 -0
  54. sqlguard_ninja/privacy/__init__.py +9 -0
  55. sqlguard_ninja/privacy/rules.py +286 -0
  56. sqlguard_ninja/privacy/suggest.py +46 -0
  57. sqlguard_ninja/py.typed +0 -0
  58. sqlguard_ninja/query_cost/__init__.py +1 -0
  59. sqlguard_ninja/query_cost/postgres.py +226 -0
  60. sqlguard_ninja/query_cost/static.py +241 -0
  61. sqlguard_ninja/reporting/__init__.py +1 -0
  62. sqlguard_ninja/reporting/markdown.py +137 -0
  63. sqlguard_ninja-0.1.0.dist-info/METADATA +370 -0
  64. sqlguard_ninja-0.1.0.dist-info/RECORD +68 -0
  65. sqlguard_ninja-0.1.0.dist-info/WHEEL +4 -0
  66. sqlguard_ninja-0.1.0.dist-info/entry_points.txt +2 -0
  67. sqlguard_ninja-0.1.0.dist-info/licenses/LICENSE +202 -0
  68. sqlguard_ninja-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +56 -0
@@ -0,0 +1,243 @@
1
+ defaults:
2
+ role: support_analyst
3
+ dialects: [postgres, sqlite]
4
+ category: legitimate_complex
5
+ expected: allow
6
+ reason: none
7
+ cases:
8
+ # ---- legitimate complex queries that must not be blocked ----
9
+ - id: LEG-001
10
+ sql: SELECT o.status, COUNT(*) AS n, SUM(o.total_amount) AS revenue FROM orders o WHERE o.tenant_id = 1 GROUP BY o.status HAVING COUNT(*) > 2 ORDER BY revenue DESC LIMIT 10
11
+ explanation: Aggregation with HAVING and ORDER BY alias.
12
+ - id: LEG-002
13
+ sql: SELECT o.id, o.total_amount, RANK() OVER (PARTITION BY o.customer_id ORDER BY o.total_amount DESC) AS r FROM orders o WHERE o.tenant_id = 1
14
+ explanation: Window function over permitted columns.
15
+ - id: LEG-003
16
+ sql: WITH recent AS (SELECT o.id, o.customer_id FROM orders o WHERE o.tenant_id = 1 AND o.status <> 'cancelled'), counts AS (SELECT r.customer_id, COUNT(*) AS n FROM recent r GROUP BY r.customer_id) SELECT c.full_name, k.n FROM counts k JOIN customers c ON c.id = k.customer_id AND c.tenant_id = 1 ORDER BY k.n DESC LIMIT 20
17
+ explanation: Multi-CTE report joining customers.
18
+ - id: LEG-004
19
+ sql: SELECT p.name, SUM(i.quantity * i.unit_price) AS sales FROM order_items i JOIN products p ON p.id = i.product_id AND p.tenant_id = i.tenant_id WHERE i.tenant_id = 1 GROUP BY p.name ORDER BY sales DESC LIMIT 5
20
+ explanation: Revenue by product with arithmetic.
21
+ - id: LEG-005
22
+ sql: SELECT CASE WHEN o.total_amount > 500 THEN 'large' WHEN o.total_amount > 100 THEN 'medium' ELSE 'small' END AS bucket, COUNT(*) FROM orders o WHERE o.tenant_id = 1 GROUP BY 1
23
+ explanation: CASE bucketing.
24
+ - id: LEG-006
25
+ sql: SELECT COALESCE(s.carrier, 'unshipped') AS carrier, COUNT(*) FROM orders o LEFT JOIN shipments s ON s.order_id = o.id AND s.tenant_id = 1 WHERE o.tenant_id = 1 GROUP BY 1
26
+ explanation: Outer join with COALESCE.
27
+ - id: LEG-007
28
+ sql: SELECT t.priority, COUNT(*) FROM support_tickets t WHERE t.tenant_id = 1 AND t.status = 'open' GROUP BY t.priority
29
+ explanation: Ticket triage summary.
30
+ - id: LEG-008
31
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.status IN ('paid', 'shipped') AND o.total_amount BETWEEN 10 AND 100
32
+ explanation: IN list and BETWEEN on non-tenant columns.
33
+ - id: LEG-009
34
+ sql: SELECT p.sku, p.name FROM products p WHERE p.tenant_id = 1 AND p.name LIKE 'Product 1%' ORDER BY p.sku
35
+ explanation: Prefix LIKE.
36
+ - id: LEG-010
37
+ sql: SELECT c.loyalty_tier, COUNT(DISTINCT o.customer_id) FROM customers c JOIN orders o ON o.customer_id = c.id AND o.tenant_id = c.tenant_id WHERE c.tenant_id = 1 GROUP BY c.loyalty_tier
38
+ explanation: Distinct count across a join.
39
+ - id: LEG-011
40
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND NOT EXISTS (SELECT 1 FROM shipments s WHERE s.order_id = o.id AND s.tenant_id = 1)
41
+ explanation: Anti-join via NOT EXISTS.
42
+ - id: LEG-012
43
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 INTERSECT SELECT s.order_id FROM shipments s WHERE s.tenant_id = 1
44
+ explanation: INTERSECT with both arms pinned.
45
+ - id: LEG-013
46
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 EXCEPT SELECT p.order_id FROM payments p WHERE p.tenant_id = 1
47
+ explanation: EXCEPT with both arms pinned.
48
+ - id: LEG-014
49
+ sql: SELECT ROUND(AVG(o.total_amount), 2) AS avg_order, MAX(o.created_at) AS last_order FROM orders o WHERE o.tenant_id = 1
50
+ explanation: Scalar aggregates.
51
+ - id: LEG-015
52
+ sql: SELECT UPPER(c.country) AS country, LOWER(c.email_domain) AS domain, LENGTH(c.full_name) AS name_len FROM customers c WHERE c.tenant_id = 1
53
+ explanation: Common scalar functions.
54
+ - id: LEG-016
55
+ role: hr_analyst
56
+ sql: SELECT e.department, e.title, COUNT(*) AS headcount, AVG(e.salary) AS avg_salary FROM employees e GROUP BY e.department, e.title ORDER BY headcount DESC
57
+ explanation: HR headcount and average salary.
58
+ - id: LEG-017
59
+ role: hr_analyst
60
+ sql: SELECT m.full_name AS manager, COUNT(e.id) AS reports FROM employees e JOIN employees m ON m.id = e.manager_id GROUP BY m.full_name
61
+ explanation: Self-join on a small table.
62
+ - id: LEG-018
63
+ role: finance_analyst
64
+ sql: SELECT o.currency, p.status, SUM(p.amount) FROM payments p JOIN orders o ON o.id = p.order_id AND o.tenant_id = p.tenant_id WHERE p.tenant_id = 1 GROUP BY o.currency, p.status
65
+ explanation: Finance roll-up.
66
+ - id: LEG-019
67
+ sql: SELECT o.id, o.created_at FROM orders o WHERE o.tenant_id = 1 ORDER BY o.created_at DESC LIMIT 50 OFFSET 50
68
+ explanation: Pagination.
69
+ - id: LEG-020
70
+ sql: SELECT DISTINCT s.carrier FROM shipments s WHERE s.tenant_id = 1
71
+ explanation: DISTINCT on a filtered query.
72
+ - id: LEG-021
73
+ sql: SELECT o.id, CAST(o.total_amount AS INTEGER) AS whole FROM orders o WHERE o.tenant_id = 1
74
+ explanation: CAST in projection.
75
+ - id: LEG-022
76
+ sql: SELECT o.status, COUNT(*) FILTER (WHERE o.total_amount > 100) AS big FROM orders o WHERE o.tenant_id = 1 GROUP BY o.status
77
+ dialects: [postgres]
78
+ explanation: Aggregate FILTER clause.
79
+ - id: LEG-023
80
+ sql: SELECT DATE_TRUNC('month', o.created_at) AS m, COUNT(*) FROM orders o WHERE o.tenant_id = 1 GROUP BY 1 ORDER BY 1
81
+ dialects: [postgres]
82
+ explanation: Monthly series (PostgreSQL).
83
+ - id: LEG-024
84
+ sql: SELECT strftime('%Y-%m', o.created_at) AS m, COUNT(*) FROM orders o WHERE o.tenant_id = 1 GROUP BY 1 ORDER BY 1
85
+ dialects: [sqlite]
86
+ explanation: Monthly series (SQLite).
87
+ - id: LEG-025
88
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.status ILIKE 'PAID'
89
+ dialects: [postgres]
90
+ explanation: ILIKE (PostgreSQL).
91
+ - id: LEG-026
92
+ sql: SELECT DISTINCT ON (o.customer_id) o.customer_id, o.id FROM orders o WHERE o.tenant_id = 1 ORDER BY o.customer_id, o.created_at DESC
93
+ dialects: [postgres]
94
+ explanation: DISTINCT ON (PostgreSQL).
95
+ - id: LEG-027
96
+ sql: SELECT o.id, o.total_amount::numeric(10,0) AS whole FROM orders o WHERE o.tenant_id = 1
97
+ dialects: [postgres]
98
+ explanation: PostgreSQL cast operator.
99
+ - id: LEG-028
100
+ sql: SELECT p.name FROM products p WHERE p.tenant_id = 1 AND p.name GLOB 'Product*'
101
+ dialects: [sqlite]
102
+ explanation: GLOB (SQLite).
103
+ - id: LEG-029
104
+ sql: SELECT o.id || '-' || o.status AS label FROM orders o WHERE o.tenant_id = 1
105
+ explanation: String concatenation operator.
106
+ - id: LEG-030
107
+ sql: SELECT o.customer_id, SUM(o.total_amount) AS total FROM orders o WHERE o.tenant_id = 1 GROUP BY o.customer_id HAVING SUM(o.total_amount) > 1000
108
+ explanation: HAVING on aggregate.
109
+
110
+ # ---- safe queries that resemble malicious ones ----
111
+ - id: LOOK-001
112
+ sql: SELECT t.id FROM support_tickets t WHERE t.tenant_id = 1 AND t.subject = 'DROP TABLE request from customer'
113
+ category: lookalike_safe
114
+ explanation: SQL keywords inside a string literal.
115
+ - id: LOOK-002
116
+ sql: SELECT t.id FROM support_tickets t WHERE t.tenant_id = 1 AND t.body LIKE '%; DELETE FROM%'
117
+ category: lookalike_safe
118
+ explanation: Searching tickets for injection text.
119
+ - id: LOOK-003
120
+ sql: SELECT o.id AS "drop", o.status AS "delete" FROM orders o WHERE o.tenant_id = 1
121
+ category: lookalike_safe
122
+ explanation: Keyword-named aliases.
123
+ - id: LOOK-004
124
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.status = 'OR 1=1'
125
+ category: lookalike_safe
126
+ explanation: Tautology text inside a literal.
127
+ - id: LOOK-005
128
+ sql: SELECT 'information_schema' AS source, o.id FROM orders o WHERE o.tenant_id = 1
129
+ category: lookalike_safe
130
+ explanation: Catalog name as a literal.
131
+ - id: LOOK-006
132
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.status <> 'deleted'
133
+ category: lookalike_safe
134
+ explanation: Status value 'deleted'.
135
+ - id: LOOK-007
136
+ sql: SELECT o.id, 'UPDATE orders SET status' AS note FROM orders o WHERE o.tenant_id = 1
137
+ category: lookalike_safe
138
+ explanation: Write statement text inside a literal.
139
+ - id: LOOK-008
140
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.id = 1 -- AND 1=1
141
+ category: lookalike_safe
142
+ explanation: Tautology in a trailing comment.
143
+ - id: LOOK-009
144
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND 1 = 1
145
+ category: lookalike_safe
146
+ explanation: A tautology combined with AND does not weaken the tenant pin.
147
+ - id: LOOK-010
148
+ sql: SELECT c.email_domain FROM customers c WHERE c.tenant_id = 1 AND c.email_domain = 'card_number.example'
149
+ category: lookalike_safe
150
+ explanation: Sensitive column name inside a literal.
151
+ - id: LOOK-011
152
+ sql: WITH sel AS (SELECT o.id FROM orders o WHERE o.tenant_id = 1) SELECT sel.id AS "union" FROM sel
153
+ category: lookalike_safe
154
+ explanation: Keyword-like CTE and alias names.
155
+ - id: LOOK-012
156
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 AND o.status = 'a''; DROP TABLE orders; --'
157
+ category: lookalike_safe
158
+ explanation: Escaped quote keeps the payload inside the string.
159
+
160
+ # ---- query cost ----
161
+ - id: CST-001
162
+ role: platform_admin
163
+ sql: SELECT o.id, i.id FROM orders o CROSS JOIN order_items i
164
+ category: cost
165
+ expected: deny
166
+ reason: cost
167
+ classification: malicious
168
+ explanation: Cartesian product of two very large tables with no filter.
169
+ - id: CST-002
170
+ role: platform_admin
171
+ sql: SELECT o.id FROM orders o, shipments s
172
+ category: cost
173
+ expected: deny
174
+ reason: cost
175
+ classification: malicious
176
+ explanation: Comma join without a join predicate.
177
+ - id: CST-003
178
+ role: platform_admin
179
+ sql: SELECT o.id FROM orders o, shipments s WHERE s.order_id = o.id AND o.status = 'new'
180
+ category: cost
181
+ explanation: Comma join linked through WHERE is a normal join.
182
+ - id: CST-004
183
+ role: platform_admin
184
+ sql: SELECT o.id FROM orders o WHERE o.status = 'new' ORDER BY o.created_at LIMIT 100
185
+ category: cost
186
+ explanation: Filtered, sorted, limited.
187
+ - id: CST-005
188
+ sql: SELECT x FROM generate_series(1, 100000000) AS g(x)
189
+ dialects: [postgres]
190
+ category: cost
191
+ expected: review
192
+ reason: cost
193
+ classification: sensitive
194
+ explanation: Very large row generator.
195
+ - id: CST-006
196
+ sql: SELECT x FROM generate_series(1, 10) AS g(x)
197
+ dialects: [postgres]
198
+ category: cost
199
+ explanation: Small row generator.
200
+ - id: CST-007
201
+ role: reporting_agent
202
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 LIMIT 5000
203
+ category: cost
204
+ expected: deny
205
+ reason: structure_limit
206
+ classification: policy_violation
207
+ explanation: LIMIT above the reporting agent's maximum of 1000.
208
+ - id: CST-008
209
+ role: reporting_agent
210
+ sql: SELECT o.id FROM orders o WHERE o.tenant_id = 1 LIMIT 500
211
+ category: cost
212
+ explanation: Within the limit.
213
+ - id: CST-009
214
+ role: reporting_agent
215
+ sql: SELECT o.id FROM orders o JOIN order_items a ON a.order_id = o.id AND a.tenant_id = 1 JOIN order_items b ON b.order_id = o.id AND b.tenant_id = 1 JOIN products p ON p.id = a.product_id AND p.tenant_id = 1 JOIN products q ON q.id = b.product_id AND q.tenant_id = 1 JOIN customers c ON c.id = o.customer_id AND c.tenant_id = 1 WHERE o.tenant_id = 1
216
+ category: cost
217
+ expected: deny
218
+ reason: structure_limit
219
+ classification: policy_violation
220
+ explanation: Five joins exceed the reporting agent's max_joins of 4.
221
+ - id: CST-010
222
+ role: platform_admin
223
+ sql: WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < 10) SELECT i FROM n
224
+ category: cost
225
+ explanation: Bounded recursion.
226
+ - id: CST-011
227
+ sql: SELECT REPEAT('x', 100000000)
228
+ dialects: [postgres]
229
+ category: cost
230
+ expected: review
231
+ reason: cost
232
+ classification: sensitive
233
+ explanation: Allocates a 100 MB string.
234
+ - id: CST-012
235
+ role: platform_admin
236
+ sql: SELECT a.id FROM orders a JOIN orders b ON a.customer_id = b.customer_id
237
+ category: cost
238
+ expected: review
239
+ reason: cost
240
+ classification: sensitive
241
+ explanation: >-
242
+ Unfiltered self-join of a 10M-row table on a non-unique key can produce a very large
243
+ intermediate result; it should be reviewed before running.