VYPR
Critical severityNVD Advisory· Published Feb 27, 2026· Updated Mar 5, 2026

CVE-2026-28370

CVE-2026-28370

Description

In the query parser in OpenStack Vitrage before 12.0.1, 13.0.0, 14.0.0, and 15.0.0, a user allowed to access the Vitrage API may trigger code execution on the Vitrage service host as the user the Vitrage service runs under. This may result in unauthorized access to the host and further compromise of the Vitrage service. All deployments exposing the Vitrage API are affected. This occurs in _create_query_function in vitrage/graph/query.py.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OpenStack Vitrage query parser before 12.0.1, 13.0.0, 14.0.0, 15.0.0 allows remote code execution via crafted API queries due to unsafe eval() usage.

Root

Cause The vulnerability resides in the query parser of OpenStack Vitrage, specifically in the _create_query_function method within vitrage/graph/query.py. The code used eval() to dynamically evaluate query expressions, as seen in the original implementation [4]. This allowed an attacker to inject arbitrary Python code through the query string, leading to remote code execution on the Vitrage service host [1][4].

Exploitation

An attacker with access to the Vitrage API can craft a malicious query containing Python code. The eval() call in the create_predicate function would execute the payload as if it were part of the application. The vulnerability affects all deployments exposing the Vitrage API, and no additional authentication beyond API access is required [1][3].

Impact

Successful exploitation allows the attacker to execute arbitrary commands under the same user account as the Vitrage service. This can lead to unauthorized access to the host, data exfiltration, and further compromise of the Vitrage service and potentially other services on the same host [1][3].

Mitigation

The fix replaces the dangerous eval() with safe function matching using a dictionary mapping operators to functions (e.g., operator.lt), ensuring only predefined operations are allowed [2]. Patched versions are 12.0.1 and later (the commit was merged for 2023.1 and subsequent releases). Users should upgrade immediately or restrict API access to trusted users only [2][3].

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vitragePyPI
>= 15.0.0.0rc1, < 15.0.115.0.1
vitragePyPI
>= 14.0.0.0rc1, < 14.0.114.0.1
vitragePyPI
>= 13.0.0.0rc1, < 13.0.113.0.1
vitragePyPI
< 12.0.112.0.1

Affected products

2
  • OpenStack/Vitragellm-create
    Range: <12.0.1, <13.0.0, <14.0.0, <15.0.0
  • OpenStack/Vitragev5
    Range: 0

Patches

1
89df4bd2ffda

Replace eval with function matching

https://github.com/openstack/vitrageDmitriy RabotyagovSep 30, 2025via ghsa
2 files changed · +42 35
  • releasenotes/notes/grap_query_eval_fixup-9232ce40ad85993e.yaml+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +security:
    +  - |
    +    A security issue in the entity graph querying mechanism has been fixed.
    +    This change hardens the query parser against malicious input.
    
  • vitrage/graph/query.py+37 35 modified
    @@ -12,6 +12,7 @@
     # License for the specific language governing permissions and limitations
     # under the License.
     
    +import operator
     from oslo_log import log as logging
     
     from vitrage.common.exception import VitrageError
    @@ -21,13 +22,21 @@
     operators = [
         '<',
         '<=',
    -    # '=',
         '==',
         '!=',
         '>=',
         '>',
     ]
     
    +ops = {
    +    '<': operator.lt,
    +    '<=': operator.le,
    +    '==': operator.eq,
    +    '!=': operator.ne,
    +    '>=': operator.ge,
    +    '>': operator.gt,
    +}
    +
     logical_operations = [
         'and',
         'or'
    @@ -64,57 +73,50 @@ def create_predicate(query_dict):
         :return: a predicate "match(item)"
         """
         try:
    -        expression = _create_query_expression(query=query_dict)
    -        LOG.debug('create_predicate::%s', expression)
    -        expression = 'lambda item: ' + expression
    -        return eval(expression)
    +        return _create_query_function(query=query_dict)
         except Exception as e:
             LOG.error('invalid query format %s. Exception: %s',
                       query_dict, e)
             raise VitrageError('invalid query format %s. Exception: %s',
                                query_dict, e)
     
     
    -def _create_query_expression(query, parent_operator=None):
    -    expressions = []
    +def _create_query_function(query, parent_operator=None):
     
         # First element or element under logical operation
         if not parent_operator and isinstance(query, dict):
             (key, value) = query.copy().popitem()
    -        return _create_query_expression(value, key)
    +        return _create_query_function(value, key)
     
         # Continue recursion on logical (and/or) operation
         elif parent_operator in logical_operations and isinstance(query, list):
    -        for val in query:
    -            expressions.append(_create_query_expression(val))
    -        return _join_logical_operator(parent_operator, expressions)
    +        predicates = [_create_query_function(val) for val in query]
    +
    +        if not predicates:
    +            return lambda item: False
    +
    +        if parent_operator == 'and':
    +            return lambda item: all(p(item) for p in predicates)
    +        elif parent_operator == 'or':
    +            return lambda item: any(p(item) for p in predicates)
     
         # Recursion evaluate leaf (stop condition)
         elif parent_operator in operators:
    -        for key, val in query.items():
    -            expressions.append('item.get(' + _evaluable_str(key) + ')' +
    -                               parent_operator + ' ' + _evaluable_str(val))
    -        return _join_logical_operator('and', expressions)
    +        predicates = []
    +        op_func = ops[parent_operator]
    +        for field, value in query.items():
    +            predicates.append(
    +                lambda item, f=field, v=value: op_func(item.get(f), v)
    +            )
    +
    +        # Multiple conditions under a comparison operator are implicitly 'and'
    +        if len(predicates) > 1:
    +            return lambda item: all(p(item) for p in predicates)
    +        elif predicates:
    +            return predicates[0]
    +        else:
    +            return lambda item: False
    +
         else:
             raise VitrageError('invalid partial query format',
                                parent_operator, query)
    -
    -
    -def _evaluable_str(value):
    -    """wrap string/unicode with back tick"""
    -    if isinstance(value, str):
    -        return '\'' + value + '\''
    -    else:
    -        return str(value)
    -
    -
    -def _join_logical_operator(op, expressions):
    -    """Create an expressions string
    -
    -    Example input:
    -        op='AND'
    -        expressions=['a == b', 'c < d']
    -    Example output: (a == b AND c < d)
    -    """
    -    separator = ' ' + op + ' '
    -    return '(' + separator.join(expressions) + ')'
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.