@@ -20,6 +20,16 @@ class IPGIndexTranslator(Interface):
2020 a fallback when the index is not in the ``IndexRegistry``.
2121 - ``catalog.py``: ``_extract_from_translators()`` calls ``extract()``
2222 for all registered translators during indexing.
23+
24+ **Security contract:**
25+
26+ Implementations MUST use psycopg parameterized queries for all
27+ user-supplied values. The ``query()`` method returns a raw SQL
28+ fragment that is appended directly to the WHERE clause — never
29+ interpolate user input into this fragment. Only use ``%(name)s``
30+ placeholders with corresponding entries in the returned params dict.
31+ Index/column identifiers in the fragment should be hardcoded constants
32+ or validated against ``columns.validate_identifier()``.
2333 """
2434
2535 def extract (obj , index_name ):
@@ -32,6 +42,10 @@ def extract(obj, index_name):
3242 def query (index_name , query_value , query_options ):
3343 """Translate a ZCatalog query dict into a SQL fragment + params.
3444
45+ The returned SQL fragment is inserted directly into a WHERE clause.
46+ All user-supplied values MUST use ``%(name)s`` parameter placeholders
47+ — never string-format values into the SQL.
48+
3549 Returns (sql_fragment, params_dict), e.g.:
3650 ("pgcatalog_to_timestamptz(idx->>'event_start') <= %(drir_date)s",
3751 {"drir_date": query_value})
@@ -40,5 +54,8 @@ def query(index_name, query_value, query_options):
4054 def sort (index_name ):
4155 """Return SQL expression for ORDER BY, or None if not sortable.
4256
57+ The returned expression is inserted directly into an ORDER BY clause.
58+ Only use hardcoded column references — never interpolate user input.
59+
4360 Returns e.g.: "pgcatalog_to_timestamptz(idx->>'event_start')"
4461 """
0 commit comments