diff --git a/odoo/addons/test_lint/tests/_odoo_checker_sql_injection.py b/odoo/addons/test_lint/tests/_odoo_checker_sql_injection.py index 4af86bb6dd1..ad281ef346d 100644 --- a/odoo/addons/test_lint/tests/_odoo_checker_sql_injection.py +++ b/odoo/addons/test_lint/tests/_odoo_checker_sql_injection.py @@ -153,6 +153,13 @@ class OdooBaseChecker(BaseChecker): return self._is_constexpr(node.value, args_allowed=args_allowed, position=position) elif isinstance(node, astroid.BinOp): # recusively infer both side of the operation. Failing if either side is not inferable left_operand = self._is_constexpr(node.left, args_allowed=args_allowed) + # This case allows to always consider a string formatted with %d to be safe + if node.op == '%' and \ + isinstance(node.left, astroid.Const) and \ + node.left.pytype() == 'builtins.str' and \ + '%d' in node.left.value and \ + not '%s' in node.left.value: + return True right_operand = self._is_constexpr(node.right, args_allowed=args_allowed) return left_operand and right_operand elif isinstance(node, astroid.Name) or isinstance(node, astroid.AssignName): # Variable: find the assignement instruction in the AST and infer its value. diff --git a/odoo/addons/test_lint/tests/test_checkers.py b/odoo/addons/test_lint/tests/test_checkers.py index d7abd8f160f..42ca4e9c42e 100644 --- a/odoo/addons/test_lint/tests/test_checkers.py +++ b/odoo/addons/test_lint/tests/test_checkers.py @@ -435,3 +435,10 @@ class TestSqlLint(TransactionCase): """) with self.assertMessages("sql-injection"): checker.visit_call(node) + + node = _odoo_checker_sql_injection.astroid.extract_node(""" + def formatNumber(var): + cr.execute('LIMIT %d' % var)#@ + """) + with self.assertMessages(): + checker.visit_call(node)