SQL Instrument
SqlInstrument
SqlInstrument
is an instrument that sanitizes SQL queries by:
- Whitelisting only certain columns and SQL functions in the
SELECT
clause. - Enforcing row-level filters by appending specific
WHERE
conditions (e.g., tenant or user-based). - Discarding any disallowed columns or functions from the final SQL.
The SQL Instrument uses SQLGlot.
Configuration
Argument | Type | Description |
---|---|---|
allowed_columns |
list[str] |
List of columns that are permitted to appear in the final SELECT or WHERE . Any column reference not in this list will be removed from the query. |
allowed_functions |
list[str] |
List of SQL function names allowed in the SELECT expressions. For example, ["COUNT", "CONCAT", "SUBSTR"] . Any function call not in this list will be removed from the final query. |
row_filter |
list[str] |
One or more SQL boolean expressions (without the WHERE keyword) that will be appended to the existing WHERE clause. Multiple filters get combined with an AND . For example, ["tenant_id = 100", "deleted_at IS NULL"] . If these filters reference columns not in allowed_columns , they may also be sanitized away or cause the filter to be dropped. |
Usage Example
examples/sql_queries.py
import sys
from eunomia.instruments import RbacInstrument, SqlInstrument
from eunomia.orchestra import Orchestra
# Define policy for "alice"
instrument_alice = SqlInstrument(
allowed_columns=["id", "email", "status", "first_name", "last_name", "total"],
allowed_functions=["CONCAT", "COUNT"], # Alice can use CONCAT and COUNT
row_filter=["tenant_id = 100", "first_name = 'Mario' "], # multiple row filters
)
# Define policy for "bob"
instrument_bob = SqlInstrument(
allowed_columns=["id", "email", "tenant_id"],
allowed_functions=["COUNT"], # Bob can only use COUNT, not CONCAT
row_filter=["tenant_id = 200"], # single row filter
)
# Create the Orchestra with an RBAC layer:
orchestra = Orchestra(
instruments=[
RbacInstrument(
role="alice",
instruments=[instrument_alice],
),
RbacInstrument(
role="bob",
instruments=[instrument_bob],
),
]
)
test_query = """
SELECT
id,
email,
secret_col,
CONCAT(first_name, ' ', last_name) AS full_name,
COUNT(id) AS total,
SUBSTR(email, 1, 5) AS partial_email
FROM users
WHERE status = 'active' OR is_test = 1
"""
if __name__ == "__main__":
# Run for "alice"
try:
rewritten_for_alice = orchestra.run(test_query, role="alice")
print("=== ALICE ORIGINAL QUERY ===")
print(test_query)
print("\n=== ALICE REWRITTEN QUERY ===")
print(rewritten_for_alice)
except Exception as e:
print("Alice rewrite error:", e)
# Run for "bob"
try:
rewritten_for_bob = orchestra.run(test_query, role="bob")
print("\n=== BOB ORIGINAL QUERY ===")
print(test_query)
print("\n=== BOB REWRITTEN QUERY ===")
print(rewritten_for_bob)
except Exception as e:
print("Bob rewrite error:", e)
This snippet will:
- Drop any references to columns not in
allowed_columns
(e.g.,secret_col
, if not added to the list). - Remove function calls not in
allowed_functions
(e.g.,SUBSTR
here). - Append the row filters
(tenant_id = 100 AND deleted_at IS NULL)
to the existingWHERE
condition, provided those columns exist inallowed_columns
.
As a result, you get a final sanitized SQL that enforces row-level security and strips out disallowed columns or functions.