Python Coding Violations
Cyclopt analyzes your Python code to identify coding and security violations. Each check below lists its Rule ID, a description, a severity, and its category.
Use a check's Rule ID to silence a finding in code with a cyclopt-ignore comment, or to exclude it project-wide in the configuration file.
Not In Loop
Rule ID: E0103
Description: Break or continue keywords are used outside a loop
def check_value(value):
if value > 10:
continue
print(value)
for i in range(3):
check_value(i)
for i in range(3):
if i == 2:
continue
print(i)
Function Redefined
Rule ID: E0102
Description: A function / class / method is redefined
def greet():
print('Hello')
def greet():
print('Hi again')
def say_hello():
print('Hello')
def say_goodbye():
print('Goodbye')
Continue In Finally
Rule ID: E0116
Description: Emitted when the continue keyword is found inside a finally clause, which is a SyntaxError
while running:
try:
pass
finally:
continue # [continue-in-finally]
while running:
try:
pass
except KeyError:
pass
else:
continue
Abstract Class Instantiated
Rule ID: E0110
Description: An abstract class with abc.ABCMeta or abc.ABC as metaclass has abstract methods and is instantiated
import abc
class Vehicle(abc.ABC):
@abc.abstractmethod
def start_engine(self):
pass
car = Vehicle() # [abstract-class-instantiated]
import abc
class Vehicle(abc.ABC):
@abc.abstractmethod
def start_engine(self):
pass
class Car(Vehicle):
def start_engine(self):
print("Vroom")
car = Car()
Star Needs Assignment Target
Rule ID: E0114
Description: Can use starred expression only in assignment target. Emitted when a star expression is not used in an assignment target
planets = *["Mercury", "Venus", "Earth"] # [star-needs-assignment-target]
mercury, *rest_of_planets = ["Mercury", "Venus", "Earth"]
Duplicate Argument Name
Rule ID: E0108
Description: Duplicate argument names in function definitions are syntax errors
def get_animals(dog, cat, dog): # [duplicate-argument-name]
pass
def get_animals(dog, cat, bird):
pass
Return In Init
Rule ID: E0101
Description: Explicit return in __init__ Used when the special class method __init__ has an explicit return value. __init__ magic method is a constructor of an instance, so it should construct it but not return anything.
class Multiply:
def __init__(self, x, y): # [return-in-init]
return x * y
class Multiply:
def __init__(self, x, y) -> None:
self.product = x * y
Too Many Star Expressions
Rule ID: E0112
Description: More than one starred expression in assignment. Emitted when there are more than one starred expressions (*x) in an assignment. This is a SyntaxError
*dogs, *cats = ["Labrador", "Poodle", "Sphynx"] # [too-many-star-expressions]
*labrador_and_poodle, sphynx = ["Labrador", "Poodle", "Sphynx"]
Nonlocal And Global
Rule ID: E0115
Description: Emitted when a name is both nonlocal and global
VALUE = 100
def update_value(new_value): # [nonlocal-and-global]
global VALUE
nonlocal VALUE
VALUE = new_value
print(f"Updated global value: {VALUE}")
update_value(200)
VALUE = 100
def update_value(new_value):
global VALUE
VALUE = new_value
print(f"Updated global value: {VALUE}")
update_value(200)
Used Prior Global Declaration
Rule ID: E0118
Description: Emitted when a name is used prior a global declaration, which results in an error since Python 3.6. It can't be emitted when using Python < 3.6
FRUIT = "apple"
def update_fruit():
print(FRUIT) # [used-prior-global-declaration]
global FRUIT
FRUIT = "orange"
FRUIT = "apple"
def update_fruit():
global FRUIT
FRUIT = "banana"
Assignment Outside Function
Rule ID: E0104
Description: An assignment is found outside a function or method
x = 5 # [assignment-outside-function]
def assign_value():
x = 5
Return Arg In Generator
Rule ID: E0106
Description: A return statement with an argument is found in a generator function or method. It's allowed in Python >= 3.3 but wasn't allowed earlier.
def yield_items():
for item in ['a', 'b', 'c']:
yield item
return "Finished!" # This was not allowed in Python 3.3 and earlier.
Invalid Star Assignment Target
Rule ID: E0113
Description: Starred assignment target must be in a list or tuple. Error occurs when using a star expression in invalid contexts.
*numbers = [1, 2, 3] # [invalid-star-assignment-target]
numbers = [1, 2, 3]
Bad Reversed Sequence
Rule ID: E0111
Description: The argument passed to reversed() is not a sequence. This error occurs when a non-iterable type is passed to reversed().
reversed(1234) # [bad-reversed-sequence]
reversed([1, 2, 3, 4])
Nonexistent Operator
Rule ID: E0107
Description: Attempting to use C-style increment or decrement operators (++ or --), which are not valid in Python.
x = 0
while x < 5:
print(x)
++x # [nonexistent-operator]
x = 0
while x < 5:
print(x)
x += 1
Yield Outside Function
Rule ID: E0105
Description: A yield statement is found outside a function or method, which is invalid.
for i in range(5):
yield i # [yield-outside-function]
def range_yield():
for i in range(5):
yield i
Init Is Generator
Rule ID: E0100
Description: The special method __init__ is turned into a generator, which is incorrect. __init__ cannot contain yield.
class Animal:
def __init__(self, names): # [init-is-generator]
yield from names
cat = Animal(["Tom", "Jerry"])
class Animal:
def __init__(self, names):
self.names = names
def get_names(self):
yield from self.names
cat = Animal(["Tom", "Jerry"])
for name in cat.get_names():
pass
Misplaced Format Function
Rule ID: E0119
Description: The format() function is not called on a string object directly, causing an error.
print('Hello {}').format('World') # [misplaced-format-function]
print('Hello {}'.format('World'))
Nonlocal Without Binding
Rule ID: E0117
Description: Emitted when a nonlocal variable does not have an attached name somewhere in the parent scopes
class Animal:
def get_sound(self):
nonlocal sounds # [nonlocal-without-binding]
class Animal:
sounds = ["bark", "meow"]
def get_sound(self):
nonlocal sounds
Lost Exception
Rule ID: W0150
Description: A break or a return statement is found inside the finally clause of a try...finally block, the exceptions raised in the try clause will be silently swallowed instead of being re-raised
class InfinityError(ArithmeticError):
def __init__(self):
uper().__init__("You can't reach infinity!")
def calculate_division(dividend: float, divisor: float) -> float:
try:
return dividend / divisor
except ArithmeticError as e:
raise InfinityError() from e
finally:
return float('inf') # [lost-exception]
class InfinityError(ArithmeticError):
def __init__(self):
super().__init__("You can't reach infinity!")
def calculate_division(dividend: float, divisor: float) -> float:
try:
return dividend / divisor
except ArithmeticError as e:
raise InfinityError() from e
Assert On Tuple
Rule ID: W0199
Description: A call of assert on a tuple will always evaluate to true if the tuple is not empty, and will always evaluate to false if it is
assert (42, None) # [assert-on-tuple]
val1, val2 = (42, None)
assert val1
assert val2
Assert On String Literal
Rule ID: W0129
Description: When an assert statement has a string literal as its first argument, which will cause the assert to always pass
def test_multiplication():
a = 4 * 5
assert "No MultiplicationError were raised" # [assert-on-string-literal]
def test_multiplication():
a = 4 * 5
assert a == 20
Self Assigning Variable
Rule ID: W0127
Description: Emitted when we detect that a variable is assigned to itself
temperature = 100
temperature = temperature # [self-assigning-variable]
temperature = 100
Comparison With Callable
Rule ID: W0143
Description: This message is emitted when pylint detects that a comparison with a callable was made, which might suggest that some parenthesis were omitted, resulting in potential unwanted behavior
def function_returning_a_number() -> int:
return 42
def is_forty_two(num: int = 21):
# 21 == <function function_returning_a_number at 0x7f343ff0a1f0>
return num == function_returning_a_number # [comparison-with-callable]
def function_returning_a_number() -> int:
return 42
def is_forty_two(num: int = 21):
# 21 == 42
return num == function_returning_a_number()
Dangerous Default Value
Rule ID: W0102
Description: A mutable value as list or dictionary is detected in a default value for an argument
def whats_in_the_bag(items=[]): # [dangerous-default-value]
items.append("book")
return items
def whats_in_the_bag(items=None):
if items is None:
items = []
items.append("book")
return items
Duplicate Key
Rule ID: W0109
Description: A dictionary expression binds the same key multiple times
exam_scores = {"English": 80, "Physics": 85, "English": 90} # [duplicate-key]
exam_scores = {"English": 80, "Physics": 85, "Chemistry": 90}
Useless Else On Loop
Rule ID: W0120
Description: Loops should only have an else clause if they can exit early with a break statement, otherwise the statements under else should be on the same scope as the loop itself
def find_positive_number(numbers):
for x in numbers:
if x > 0:
return x
else: # [useless-else-on-loop]
print("Did not find a positive number")
def find_positive_number(numbers):
for x in numbers:
if x > 0:
return x
print("Did not find a positive number")
Expression Not Assigned
Rule ID: W0106
Description: An expression that is not a function call is assigned to nothing. Probably something else was intended
float(3.14) == "3.14" # [expression-not-assigned]
is_equal: bool = float(3.14) == "3.14"
Confusing With Statement
Rule ID: W0124
Description: Emitted when a with statement component returns multiple values and uses name binding with as only for a part of those values, as in with ctx() as a, b. This can be misleading, since it's not clear if the context manager returns a tuple or if the node without a name binding is another context manager
with open("data.txt", "r") as f1, f2: # [confusing-with-statement]
pass
with open("data.txt", "r", encoding="utf8") as f1:
with open("log.txt", "w", encoding="utf8") as f2:
pass
Unnecessary Lambda
Rule ID: W0108
Description: The body of a lambda expression is a function call on the same argument list as the lambda itself. Such lambda expressions are in all but a few cases replaceable with the function being called in the body of the lambda
df.map(lambda x: x.upper()) # [unnecessary-lambda]
df.map(str.upper)
Assign To New Keyword
Rule ID: W0111
Description: async and await are reserved Python keywords in Python >= 3.6 versions
Redeclared Assigned Name
Rule ID: W0128
Description: Emitted when we detect that a variable was redeclared in the same assignment
ITEM, ITEM = ('apple', 'banana') # [redeclared-assigned-name]
ITEM1, ITEM2 = ('apple', 'banana')
Pointless Statement
Rule ID: W0104
Description: A statement doesn't have (or at least seems to) any effect
[4, 5, 6] # [pointless-statement]
NUMS = [4, 5, 6]
print(NUMS)
Pointless String Statement
Rule ID: W0105
Description: A string is used as a statement (which of course has no effect). This is a particular case of W0104 with its own message so you can easily disable it if you're using those strings as documentation, instead of comments
"""Function to calculate sum"""
"""Another pointless string statement""" # [pointless-string-statement]
"""Function to calculate sum"""
# A comment explaining the logic used in summing.
Unnecessary Pass
Rule ID: W0107
Description: A pass statement that can be avoided is encountered
class ValidationError(Exception):
"""This exception is raised for invalid inputs."""
pass # [unnecessary-pass]
class ValidationError(Exception):
"""This exception is raised for invalid inputs."""
Unreachable
Rule ID: W0101
Description: There is some code behind a return or raise statement, which will never be accessed
def greet_user():
return True
print("Welcome!") # [unreachable]
def greet_user():
print("Welcome!")
return True
Eval Used
Rule ID: W0123
Description: You use the eval function, to discourage its usage. Consider using ast.literal_eval for safely evaluating strings containing Python expressions from untrusted sources
eval("{1: 'a', 2: 'b'}") # [eval-used]
from ast import literal_eval
literal_eval("{1: 'a', 2: 'b'}")
Exec Used
Rule ID: W0122
Description: You use the exec statement (function for Python 3), to discourage its usage. That doesn't mean you cannot use it! It's dangerous to use this function for a user input
user_input = "John"
code = f"""eval(input('Execute this code, {user_input}: '))"""
result = exec(code) # [exec-used]
exec(result) # [exec-used]
def get_user_input(name):
return input(f"Enter code for execution, {name}: ")
user_input = "John"
allowed_globals = {"__builtins__": None}
allowed_locals = {"print": print}
# pylint: disable-next=exec-used
exec(get_user_input(user_input), allowed_globals, allowed_locals)
Using Constant Test
Rule ID: W0125
Description: Emitted when a conditional statement (If or ternary if) uses a constant value for its test
if False: # [using-constant-test]
print("Will never run.")
if True: # [using-constant-test]
print("Will always run.")
print("This statement is executed.")
Missing Parentheses For Call In Test
Rule ID: W0126
Description: Emitted when a conditional statement (If or ternary if) seems to wrongly call a function due to missing parentheses
import random
def is_sunny():
return random.choice([True, False])
if is_sunny: # [missing-parentheses-for-call-in-test]
print("It is sunny!")
import random
def is_sunny():
return random.choice([True, False])
if is_sunny():
print("It is sunny!")
Literal Comparison
Rule ID: R0123
Description: Comparing an object to a literal, which is usually what you do not want to do, since you can compare to a different literal than what was expected altogether
def is_a_banana(fruit):
return fruit is "banana" # [literal-comparison]
def is_a_banana(fruit):
return fruit == "banana"
Comparison With Itself
Rule ID: R0124
Description: Something is compared against itself
def is_a_banana(fruit):
a_banana = "banana"
return fruit == fruit # [comparison-with-itself]
def is_a_banana(fruit):
a_banana = "banana"
return a_banana == fruit
Non Ascii Name
Rule ID: C0144
Description: Name contains at least one non-ASCII unicode character
Invalid Name
Rule ID: C0103
Description: The name doesn't conform to naming rules associated to its type (constant, variable, class...)
class dog: # [invalid-name]
def Bark(self, NUMBER_OF_BARK): # [invalid-name, invalid-name]
print("Bark" * NUMBER_OF_BARK)
return NUMBER_OF_BARK
Dog = dog().Bark(10) # [invalid-name]
class Dog:
def bark(self, number_of_bark):
print("Bark" * number_of_bark)
return number_of_bark
DOG = Dog().bark(10)
Blacklisted Name
Rule ID: C0102
Description: The name is listed in the black list (unauthorized names)
Singleton Comparison
Rule ID: C0121
Description: An expression is compared to singleton values like True, False or None
lights_on = False
if lights_on == False: # [singleton-comparison]
print("Lights are off.")
lights_on = False
if not lights_on:
print("Lights are off.")
Misplaced Comparison Constant
Rule ID: C0122
Description: The constant is placed on the left side of a comparison. It is usually clearer in intent to place it in the right hand side of the comparison
Empty Docstring
Rule ID: C0112
Description: A module, function, class or method has an empty docstring (it would be too easy)
def bar(): # [empty-docstring]
""""""
def bar():
"""A simple function."""
Missing Class Docstring
Rule ID: C0115
Description: A class has no docstring. Even an empty class must have a docstring
class Car: # [missing-class-docstring]
def __init__(self, make, model):
self.make = make
self.model = model
class Car:
"""Class representing a car"""
def __init__(self, make, model):
self.make = make
self.model = model
Missing Function Docstring
Rule ID: C0116
Description: Missing function or method docstring used when a function or method has no docstring. Some special methods like __init__, protected, private functions, setters and deleters do not require a docstring. It's a good practice to describe what a function does for other programmers
import os
def list_files(): # [missing-function-docstring]
print(os.listdir())
import os
def list_files():
"""Function listing files in the current directory."""
print(os.listdir())
Missing Module Docstring
Rule ID: C0114
Description: A module has no docstring. Empty modules do not require a docstring
import json # [missing-module-docstring]
def parse_json(data):
return json.loads(data)
"""Module providing a function for parsing JSON data."""
import json
def parse_json(data):
return json.loads(data)
Unidiomatic Typecheck
Rule ID: C0123
Description: Using type() instead of isinstance() for a type check. The idiomatic way to perform an explicit type check in Python is to use isinstance(x, y) rather than type(x) == Y, type(x) is Y. Though there are unusual situations where these give different results
data = [1, 2, 3]
if type(data) is list: # [unidiomatic-typecheck]
pass
data = [1, 2, 3]
if isinstance(data, list):
pass
Access Member Before Definition
Rule ID: E0203
Description: An instance member is accessed before it's actually assigned
class Robot:
def __init__(self, power_level):
if self.power_level > 100: # [access-member-before-definition]
print("Power Overload!")
self.power_level = power_level
class Robot:
def __init__(self, power_level):
self.power_level = power_level
if self.power_level > 100:
print("Power Overload!")
Method Hidden
Rule ID: E0202
Description: A class defines a method which is hidden by an instance attribute from an ancestor class or set by some client code
class Plant:
def __init__(self, nutrients):
self.nutrients = nutrients
def nutrients(self): # [method-hidden]
pass
class Plant:
def __init__(self, nutrients):
self.nutrients = nutrients
def water_supply(self):
pass
Assigning Non Slot
Rule ID: E0237
Description: Assigning to an attribute not defined in the class slots
class Worker:
__slots__ = ("name")
def __init__(self, name, position):
self.name = name
self.position = position # [assigning-non-slot]
self.initialize()
class Worker:
__slots__ = ("name", "position")
def __init__(self, name, position):
self.name = name
self.position = position
self.initialize()
Duplicate Bases
Rule ID: E0241
Description: A class has duplicate bases
class Vehicle:
pass
class Bike(Vehicle, Vehicle): # [duplicate-bases]
pass
class Vehicle:
pass
class Bike(Vehicle):
pass
class Truck(Vehicle):
pass
Inconsistent Mro
Rule ID: E0240
Description: A class has an inconsistent method resolution order
class X:
pass
class Y(X):
pass
class Z(X, Y): # [inconsistent-mro]
pass
class X:
pass
class Y(X):
pass
class Z(Y): # or 'Y, X' but not 'X, Y'
pass
Inherit Non Class
Rule ID: E0239
Description: A class inherits from something which is not a class
class Animal(str): # [inherit-non-class]
pass
class Animal:
def __str__(self):
pass
Invalid Slots
Rule ID: E0238
Description: An invalid __slots__ is found in class. Only a string, an iterable or a sequence is permitted
class Vehicle: # [invalid-slots]
__slots__ = True
class Vehicle:
__slots__ = ("make", "model")
Invalid Slots Object
Rule ID: E0236
Description: An invalid (non-string) object occurs in __slots__
class Animal:
__slots__ = ("species", 5) # [invalid-slots-object]
class Animal:
__slots__ = ("species", "breed")
No Method Argument
Rule ID: E0211
Description: A method which should have the bound instance as first argument has no argument defined
class Dog:
def bark(): # [no-method-argument]
print("woof")
class Dog:
def bark(self):
print("woof")
No Self Argument
Rule ID: E0213
Description: A method has an attribute different the "self" as first argument. This is considered as an error since this is a so common convention that you shouldn't break it!
class Book:
def __init__(this, title): # [no-self-argument]
this.title = title
class Book:
def __init__(self, title):
self.title = title
Unexpected Special Method Signature
Rule ID: E0302
Description: Emitted when a special method was defined with an invalid number of parameters. If it has too few or too many, it might not work at all
class FileHandler:
def __enter__(self, filepath): # [unexpected-special-method-signature]
pass
def __exit__(self, exception): # [unexpected-special-method-signature]
pass
class FileHandler:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, traceback):
pass
Class Variable Slots Conflict
Rule ID: E0242
Description: A value in slots conflicts with a class variable, property or method
class Robot:
# +1: [class-variable-slots-conflict]
__slots__ = ("model", "year", "shutdown")
model = None
def __init__(self, model, year):
self.model = model
self.year = year
@property
def year(self):
return self.year
def shutdown(self):
print("Shutting down...")
class Robot:
__slots__ = ("_year", "model")
def __init__(self, model, year):
self._year = year
self.model = model
@property
def year(self):
return self._year
def shutdown(self):
print("Shutting down...")
Invalid Bool Returned
Rule ID: E0304
Description: bool does not return bool Used when a bool method returns something which is not a bool
class LightSwitch:
"""__bool__ returns a string"""
def __bool__(self): # [invalid-bool-returned]
return "on"
Invalid Bytes Returned
Rule ID: E0308
Description: bytes does not return bytes Used when a bytes method returns something which is not bytes
class DataPacket:
"""__bytes__ returns <type 'list'>"""
def __bytes__(self): # [invalid-bytes-returned]
return [1, 2, 3]
class DataPacket:
"""__bytes__ returns <type 'bytes'>"""
def __bytes__(self):
return b"data"
Invalid Format Returned
Rule ID: E0311
Description: format does not return str Used when a format method returns something which is not a string
class Temperature:
"""__format__ returns <type 'float'>"""
def __format__(self, format_spec): # [invalid-format-returned]
return 98.6
class Temperature:
"""__format__ returns <type 'str'>"""
def __format__(self, format_spec):
return "98.6°F"
Invalid Getnewargs Returned
Rule ID: E0312
Description: getnewargs does not return a tuple. Used when a getnewargs method returns something which is not a tuple
class CustomGetNewArgs:
"""__getnewargs__ returns a string"""
def __getnewargs__(self): # [invalid-getnewargs-returned]
return 'abc'
class CustomGetNewArgs:
"""__getnewargs__ returns <type 'tuple'>"""
def __getnewargs__(self):
return ('abc', 'def')
Invalid Getnewargs Ex Returned
Rule ID: E0313
Description: getnewargs_ex does not return a tuple containing (tuple, dict). Used when a getnewargs_ex method returns something which is not of the form tuple(tuple, dict)
class CustomGetNewArgsEx:
"""__getnewargs_ex__ returns tuple with incorrect argument types"""
def __getnewargs_ex__(self): # [invalid-getnewargs-ex-returned]
return (list('x'), set())
class CustomGetNewArgsEx:
"""__getnewargs_ex__ returns <type 'tuple'>"""
def __getnewargs_ex__(self):
return ((1, 2), {'x': 'y'})
Invalid Hash Returned
Rule ID: E0309
Description: hash does not return an integer. Used when a hash method returns something which is not an integer
class CustomHash:
"""__hash__ returns a list"""
def __hash__(self): # [invalid-hash-returned]
return [1, 2, 3]
class CustomHash:
"""__hash__ returns an int"""
def __hash__(self):
return 123
Invalid Index Returned
Rule ID: E0305
Description: index does not return an integer. Used when an index method returns something which is not an integer
class CustomIndex:
"""__index__ returns a float"""
def __index__(self): # [invalid-index-returned]
return 3.14
class CustomIndex:
"""__index__ returns an int"""
def __index__(self):
return 42
Non Iterator Returned
Rule ID: E0301
Description: An __iter__ method returns something which is not an iterable (i.e., lacks a __next__ method)
import random
class RandomItems:
def __init__(self, items):
self.items = items
def __iter__(self): # [non-iterator-returned]
self.idx = 0
return self
LIST_ITEMS = ['Apple', 'Banana', 'Grape']
for item in RandomItems(LIST_ITEMS):
print(item)
import random
class RandomItems:
def __init__(self, items):
self.items = items
def __iter__(self):
self.idx = 0
return self
def __next__(self):
if self.idx == len(self.items):
raise StopIteration
self.idx += 1
return self.items[self.idx - 1]
LIST_ITEMS = ['Apple', 'Banana', 'Grape']
for item in RandomItems(LIST_ITEMS):
print(item)
Invalid Length Returned
Rule ID: E0303
Description: A __len__ method returns something that is not a non-negative integer
class CustomSet:
def __init__(self, elements):
self.elements = {'a', 'b', 'c'}
def __len__(self): # [invalid-length-returned]
return -1
class CustomSet:
def __init__(self, elements):
self.elements = {'a', 'b', 'c'}
def __len__(self):
return len(self.elements)
Invalid Length Hint Returned
Rule ID: E0310
Description: length_hint does not return a non-negative integer. Used when a length_hint method returns something that is not a non-negative integer
class CustomLengthHint:
"""__length_hint__ returns a negative int"""
def __length_hint__(self): # [invalid-length-hint-returned]
return -5
class CustomLengthHint:
"""__length_hint__ returns a non-negative int"""
def __length_hint__(self):
return 20
Invalid Repr Returned
Rule ID: E0306
Description: repr does not return str Used when a repr method returns something which is not a string
class CustomRepr:
"""__repr__ returns <type 'float'>"""
def __repr__(self): # [invalid-repr-returned]
return 3.14
class CustomRepr:
"""__repr__ returns <type 'str'>"""
def __repr__(self):
return "pi"
Invalid Str Returned
Rule ID: E0307
Description: str does not return str Used when a str method returns something which is not a string
class CustomStr:
"""__str__ returns list"""
def __str__(self): # [invalid-str-returned]
return [1, 2, 3]
class CustomStr:
"""__str__ returns <type 'str'>"""
def __str__(self):
return "apple pie"
Protected Access
Rule ID: W0212
Description: A protected member (i.e. class member with a name beginning with an underscore) is accessed outside the class or a descendant of the class where it's defined
class Bird:
def __fly(self):
pass
jane = Bird()
jane.__fly() # [protected-access]
class Bird:
def __fly(self):
pass
def soar(self):
return self.__fly()
jane = Bird()
jane.soar()
Attribute Defined Outside Init
Rule ID: W0201
Description: An instance attribute is defined outside the __init__ method
class Employee:
def promote(self):
self.is_promoted = True # [attribute-defined-outside-init]
class Employee:
def __init__(self):
self.is_promoted = False
def promote(self):
self.is_promoted = True
No Init
Rule ID: W0232
Description: A class has no __init__ method, neither its parent classes
class Car:
def drive(self):
print('driving')
class Car:
def __init__(self):
self.speed = 0
def drive(self):
print('driving')
Abstract Method
Rule ID: W0223
Description: An abstract method (i.e. raise NotImplementedError) is not overridden in concrete class
import abc
class Vehicle:
@abc.abstractmethod
def start_engine(self):
pass
class Bike(Vehicle): # [abstract-method]
pass
import abc
class Vehicle:
@abc.abstractmethod
def start_engine(self):
pass
class Bike(Vehicle):
def start_engine(self):
print("Vroom")
Invalid Overridden Method
Rule ID: W0236
Description: We detect that a method was overridden in a way that does not match its base class which could result in potential bugs at runtime
class Tree:
async def grow(self, soil):
soil.enrich(self)
class Oak(Tree):
def grow(self, soil): # [invalid-overridden-method]
soil.enrich(self)
class Tree:
async def grow(self, soil):
soil.enrich(self)
class Oak(Tree):
async def grow(self, soil):
soil.enrich(self)
Arguments Differ
Rule ID: W0221
Description: A method has a different number of arguments than in the implemented interface or in an overridden method
class Sandwich:
def make(self, bread, filling):
return f'{bread} and {filling}'
class BLTSandwich(Sandwich):
def make(self, bread, filling, bacon): # [arguments-differ]
return f'{bread}, {filling}, and {bacon}'
Signature Differs
Rule ID: W0222
Description: A method signature is different than in the implemented interface or in an overridden method
class Vehicle:
def start(self, fuel=100):
print(f"Started with {fuel} units of fuel!")
class Car(Vehicle):
def start(self, fuel): # [signature-differs]
super(Vehicle, self).start(fuel)
print("Engine is now running!")
class Vehicle:
def start(self, fuel=100):
print(f"Started with {fuel} units of fuel!")
class Car(Vehicle):
def start(self, fuel=100):
super(Vehicle, self).start(fuel)
print("Engine is now running!")
Bad Staticmethod Argument
Rule ID: W0211
Description: A static method has self or a value specified in valid-classmethod-first-arg option or valid-metaclass-classmethod-first-arg option as the first argument
class Eagle:
@staticmethod
def fly(self): # [bad-staticmethod-argument]
pass
class Eagle:
@staticmethod
def fly(height):
pass
Useless Super Delegation
Rule ID: W0235
Description: Used whenever we can detect that an overridden method is useless, relying on super() delegation to do the same thing as another method from the MRO
class Bird:
def fly(self):
print("Flying")
class Sparrow(Bird):
def fly(self): # [useless-super-delegation]
super().fly()
print("Flying again")
class Bird:
def fly(self):
print("Flying")
class Sparrow(Bird):
def fly(self):
print("Sparrow in flight")
Non Parent Init Called
Rule ID: W0233
Description: An __init__ method is called on a class which is not in the direct ancestors for the analysed class
class Insect:
def __init__(self):
self.has_wings = True
class Butterfly(Insect):
def __init__(self):
super().__init__()
self.is_beautiful = True
class MonarchButterfly(Butterfly):
def __init__(self):
Insect.__init__(self) # [non-parent-init-called]
self.is_migratory = True
class Insect:
def __init__(self):
self.has_wings = True
class Butterfly(Insect):
def __init__(self):
super().__init__()
self.is_beautiful = True
class MonarchButterfly(Butterfly):
def __init__(self):
super().__init__()
self.is_migratory = True
Super Init Not Called
Rule ID: W0231
Description: An ancestor class method has an __init__ method which is not called by a derived class
class Tree:
def __init__(self, species="Tree"):
self.species = species
print(f"Planting a {self.species}")
class Oak(Tree):
def __init__(self): # [super-init-not-called]
print("Growing an oak tree")
class Tree:
def __init__(self, species="Tree"):
self.species = species
print(f"Planting a {self.species}")
class Oak(Tree):
def __init__(self):
super().__init__("Oak")
Property With Parameters
Rule ID: R0206
Description: Detected that a property also has parameters, which are useless, given that properties cannot be called with additional arguments
class Drill:
@property
def depth(self, value): # [property-with-parameters]
pass
class Drill:
@property
def depth(self):
"""Property accessed with '.depth'."""
pass
def set_depth(value):
"""Function called with .set_depth(value)."""
pass
Useless Object Inheritance
Rule ID: R0205
Description: A class inherits from object, which under Python 3 is implicit, hence can be safely removed from bases
class Plane(object): # [useless-object-inheritance]
...
class Plane: ...
No Classmethod Decorator
Rule ID: R0202
Description: A class method is defined without using the decorator syntax
class Flower:
COLORS = []
def __init__(self, color):
self.color = color
def set_colors(cls, *args):
"""classmethod to set flower colors"""
cls.COLORS = args
set_colors = classmethod(set_colors) # [no-classmethod-decorator]
class Flower:
COLORS = []
def __init__(self, color):
self.color = color
@classmethod
def set_colors(cls, *args):
"""classmethod to set flower colors"""
cls.COLORS = args
No Staticmethod Decorator
Rule ID: R0203
Description: A static method is defined without using the decorator syntax
class Mouse:
def run(self):
pass
run = staticmethod(run) # [no-staticmethod-decorator]
class Mouse:
@staticmethod
def run(self):
pass
No Self Use
Rule ID: R0201
Description: A method doesn't use its bound instance, and so could be written as a function
class Car:
def start_engine(self): # [no-self-use]
print('Engine started')
def start_engine():
print('Engine started')
Single String Used For Slots
Rule ID: C0205
Description: A class __slots__ is a simple string, rather than an iterable
class Tree: # [single-string-used-for-slots]
__slots__ = 'species'
def __init__(self, species):
self.species = species
class Tree:
__slots__ = ('species',)
def __init__(self, species):
self.species = species
Bad Classmethod Argument
Rule ID: C0202
Description: A class method has a first argument named differently than the value specified in valid-classmethod-first-arg option (default to "cls"), recommended to easily differentiate them from regular instance methods
class Vehicle:
@classmethod
def create(cls): # [bad-classmethod-argument]
return cls()
class Vehicle:
@classmethod
def create(cls):
return cls()
Bad Mcs Classmethod Argument
Rule ID: C0204
Description: A metaclass class method has a first argument named differently than the value specified in valid-metaclass-classmethod-first-arg option (default to mcs), recommended to easily differentiate them from regular instance methods
class Factory(type):
@classmethod
def make_product(thing): # [bad-mcs-classmethod-argument]
pass
class Factory(type):
@classmethod
def make_product(mcs):
pass
Bad Mcs Method Argument
Rule ID: C0203
Description: A metaclass method has a first argument named differently than the value specified in valid-classmethod-first-arg option (default to cls), recommended to easily differentiate them from regular instance methods
class Manager(type):
def assign_task(task): # [bad-mcs-method-argument]
pass
class Manager(type):
def assign_task(cls):
pass
Method Check Failed
Rule ID: F0202
Description: Pylint has been unable to check methods signature compatibility for an unexpected reason. Please report this kind if you don't make sense of it
class Device:
def start_device(self, config):
# [method-check-failed]
pass
class Device:
def start_device(self, config):
pass
Too Few Public Methods
Rule ID: R0903
Description: Class has too few public methods, so be sure it's really worth it
class Bird:
def __init__(self, species: str):
self.species = species
def fly(self):
print(f"The {self.species} is flying.")
import dataclasses
@dataclasses.dataclass
class Bird:
species: str
def fly(bird: Bird):
print(f"The {bird.species} is flying.")
Too Many Ancestors
Rule ID: R0901
Description: Class has too many parent classes, try to reduce this to get a simpler (and so easier to use) class
class Mammal: ...
class FlyingAnimal(Mammal): ...
class SwimmingAnimal(Mammal): ...
class EndangeredSpecies(Mammal): ...
# max of 7 by default, can be configured
class Bat( # [too-many-ancestors]
FlyingAnimal,
SwimmingAnimal,
EndangeredSpecies,
):
pass
class Animal:
can_fly: bool
can_swim: bool
is_endangered: bool
class Mammal(Animal):
can_fly = False
can_swim = False
is_endangered = False
class Bat(Mammal):
can_fly = True
is_endangered = True
Too Many Arguments
Rule ID: R0913
Description: A function or method takes too many arguments
def process_sensor_data( # [too-many-arguments]
accelerometer,
gyroscope,
magnetometer,
barometer,
proximity_sensor,
light_sensor,
current_time,
temperature_sensor,
):
pass
from dataclasses import dataclass
@dataclass
class Sensor:
accelerometer: float
gyroscope: float
magnetometer: float
barometer: float
@dataclass
class EnvironmentSensor:
proximity: float
light: float
temperature: float
def process_sensor_data(
motion: Sensor,
environment: EnvironmentSensor,
current_time,
):
pass
Too Many Boolean Expressions
Rule ID: R0916
Description: An if statement contains too many boolean expressions
def check_valid_triangle(a, b, c):
# +1: [too-many-boolean-expressions]
if (a > 0 and b > 0 and c > 0) and (a + b > c and a + c > b and b + c > a):
pass
def check_valid_triangle(a, b, c):
if all(x > 0 for x in [a, b, c]) and (a + b > c and a + c > b and b + c > a):
pass
Too Many Branches
Rule ID: R0912
Description: A function or method has too many branches, making it hard to follow
def map_day_to_word(day): # [too-many-branches]
if day == 1:
return 'Monday'
elif day == 2:
return 'Tuesday'
elif day == 3:
return 'Wednesday'
elif day == 4:
return 'Thursday'
elif day == 5:
return 'Friday'
elif day == 6:
return 'Saturday'
elif day == 7:
return 'Sunday'
else:
return None
def map_day_to_word(day):
return {
1: 'Monday',
2: 'Tuesday',
3: 'Wednesday',
4: 'Thursday',
5: 'Friday',
6: 'Saturday',
7: 'Sunday',
}.get(day)
Too Many Instance Attributes
Rule ID: R0902
Description: Class has too many instance attributes, try to reduce this to get a simpler (and so easier to use) class
class Car: # [too-many-instance-attributes]
def __init__(self):
self.make = 'Toyota'
self.model = 'Camry'
self.year = 2022
self.engine = 'V6'
self.transmission = 'Automatic'
self.color = 'Blue'
self.fuel_type = 'Gasoline'
self.seating_capacity = 5
self.mileage = 30000
import dataclasses
@dataclasses.dataclass
class Engine:
engine_type: str
fuel_type: str
@dataclasses.dataclass
class Car:
make: str
model: str
year: int
engine: Engine
seating_capacity: int
mileage: int
car = Car(
make='Toyota', model='Camry', year=2022, engine=Engine('V6', 'Gasoline'), seating_capacity=5, mileage=30000
)
Too Many Locals
Rule ID: R0914
Description: A function or method has too many local variables
def process_purchase_info(purchases): # [too-many-locals]
item_list = []
purchase_total = 0
discount = 0.1
shipping_cost = 5.99
tax = 0.07
final_total = 0
reward_points = 0
loyalty_bonus = 0.05
payment_method = 'Credit Card'
for item in purchases:
pass
from typing import NamedTuple
class PurchaseDetails(NamedTuple):
purchase_total: float
discount: float
shipping_cost: float
tax: float
def process_purchase_info(purchases):
purchase_details = PurchaseDetails(100, 0.1, 5.99, 0.07)
final_total = _calculate_final_total(purchase_details)
_handle_rewards_and_loyalty(purchases, final_total)
def _calculate_final_total(details: PurchaseDetails):
# logic to calculate final total
pass
def _handle_rewards_and_loyalty(purchases, final_total):
# handle reward logic
pass
Too Many Public Methods
Rule ID: R0904
Description: Class has too many public methods, try to reduce this to get a simpler (and so easier to use) class
class Game: # [too-many-public-methods]
def start(self): pass
def stop(self): pass
def reset(self): pass
def save(self): pass
def load(self): pass
def pause(self): pass
def resume(self): pass
def quit(self): pass
def restart(self): pass
def autosave(self): pass
class GameController:
def __init__(self):
self.state = 'Stopped'
def start(self):
self.state = 'Running'
def stop(self):
self.state = 'Stopped'
def pause(self):
self.state = 'Paused'
class SaveSystem:
def save(self): pass
def load(self): pass
class Game:
def __init__(self):
self.controller = GameController()
self.save_system = SaveSystem()
Too Many Return Statements
Rule ID: R0911
Description: Function contains too many return statements
def classify_grade(score):
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
def classify_grade(score):
return next((grade for score_threshold, grade in [(90, 'A'), (80, 'B'), (70, 'C'), (60, 'D')] if score >= score_threshold), 'F')
Too Many Statements
Rule ID: R0915
Description: A function or method contains too many statements
def initialize_game(): # [too-many-statements]
player = Player()
game = Game()
game.setup_board()
game.assign_pieces()
player.set_position(0, 0)
player.set_health(100)
player.set_inventory([])
enemy = Enemy()
enemy.set_position(10, 10)
enemy.set_health(100)
weapon = Weapon('sword')
armor = Armor('shield')
player.equip(weapon)
player.equip(armor)
game.start()
def initialize_game():
player = _initialize_player()
enemy = _initialize_enemy()
game = _initialize_game_environment(player, enemy)
game.start()
def _initialize_player():
player = Player()
player.set_position(0, 0)
player.set_health(100)
player.set_inventory([])
player.equip(Weapon('sword'))
player.equip(Armor('shield'))
return player
def _initialize_enemy():
enemy = Enemy()
enemy.set_position(10, 10)
enemy.set_health(100)
return enemy
def _initialize_game_environment(player, enemy):
game = Game()
game.setup_board()
game.assign_pieces()
return game
Bad Except Order
Rule ID: E0701
Description: Except clauses are not in the correct order (from the more specific to the more generic). If you don't fix the order, some exceptions may not be caught by the most specific handler.
try:
result = int(input('Enter a number: '))
except Exception:
print('An error occurred')
except ValueError: # [bad-except-order]
print('Invalid input')
try:
result = int(input('Enter a number: '))
except ValueError:
print('Invalid input')
except Exception:
print('An error occurred')
Catching Non Exception
Rule ID: E0712
Description: A class which doesn't inherit from Exception is used as an exception in an except clause
class BarError:
pass
try:
'abc' + 123
except BarError: # [catching-non-exception]
pass
class BarError(Exception):
pass
try:
'abc' + 123
except BarError:
pass
Bad Exception Context
Rule ID: E0703
Description: Using the syntax raise ... from ..., where the exception context is not an exception, nor None. This message belongs to the exceptions checker
try:
raise ValueError('Issue encountered') from 'Not an exception' # [bad-exception-context]
except ValueError:
pass
try:
raise ValueError('Issue encountered') from KeyError('Wrong key')
except ValueError:
pass
Notimplemented Raised
Rule ID: E0711
Description: NotImplemented is raised instead of NotImplementedError
class Bird:
def fly(self):
raise NotImplemented # [notimplemented-raised]
class Bird:
def fly(self):
raise NotImplementedError
Raising Bad Type
Rule ID: E0702
Description: Something which is neither a class, an instance or a string is raised (i.e. a TypeError will be raised)
class ImpossibleCalculationError(OverflowError):
def __init__(self):
super().__init__("Calculation exceeded the limits!")
def calculate_area(radius: float) -> float:
try:
return 3.14 * radius ** 2
except OverflowError as e:
raise None # [raising-bad-type]
class ImpossibleCalculationError(OverflowError):
def __init__(self):
super().__init__("Calculation exceeded the limits!")
def calculate_area(radius: float) -> float:
try:
return 3.14 * radius ** 2
except OverflowError as e:
raise ImpossibleCalculationError() from e
Raising Non Exception
Rule ID: E0710
Description: A new style class which doesn't inherit from BaseException is raised
raise list # [raising-non-exception]
raise ValueError("Invalid operation!")
Misplaced Bare Raise
Rule ID: E0704
Description: A bare raise is not used inside an except clause. This generates an error, since there are no active exceptions to be reraised. An exception to this rule is represented by a bare raise inside a finally clause, which might work, as long as an exception is raised inside the try block, but it is nevertheless a code smell that must not be relied upon
def validate_input(x):
if x == '':
raise # [misplaced-bare-raise]
def validate_input(x):
if x == '':
raise ValueError(f"Input cannot be empty: {x}")
Duplicate Except
Rule ID: W0705
Description: An except catches a type that was already caught by a previous handler
try:
1 + 'a'
except TypeError:
pass
except TypeError: # [duplicate-except]
pass
try:
1 + 'a'
except TypeError:
pass
Broad Except
Rule ID: W0703
Description: An except catches a too general exception, possibly burying unrelated errors
try:
1 / 0
except Exception: # [broad-except]
pass
try:
1 / 0
except ZeroDivisionError: # handle specific error
pass
Raise Missing From
Rule ID: W0707
Description: Consider explicitly re-raising using the 'from' keyword. Python 3's exception chaining ensures the traceback shows both the current exception and the original one. Not using 'raise from' leads to an inaccurate traceback that might obscure the true source of the error.
try:
open('non_existent_file.txt')
except FileNotFoundError as e:
raise IOError("File cannot be opened") # [raise-missing-from]
try:
open('non_existent_file.txt')
except FileNotFoundError as e:
raise IOError("File cannot be opened") from e
Raising Format Tuple
Rule ID: W0715
Description: Passing multiple arguments to an exception constructor, with one of them being a tuple for string formatting, leads to incorrect behavior. Correct the formatting by using '%' operator inside the string.
raise TypeError("Unsupported operand type(s) %s %s", ("int", "str")) # [raising-format-tuple]
raise TypeError("Unsupported operand type(s) %s %s" % ("int", "str"))
Binary Op Exception
Rule ID: W0711
Description: Avoid using except A or B for catching exceptions. To catch multiple exceptions, use the tuple syntax: except (A, B).
try:
int('abc')
except ValueError or TypeError: # [binary-op-exception]
pass
try:
int('abc')
except (ValueError, TypeError):
pass
Wrong Exception Operation
Rule ID: W0716
Description: Binary operations between exceptions like ValueError + TypeError are invalid. Use a tuple (ValueError, TypeError) to catch multiple exceptions.
try:
float('abc')
except ValueError + TypeError: # [wrong-exception-operation]
pass
try:
float('abc')
except (ValueError, TypeError):
pass
Bare Except
Rule ID: W0702
Description: Avoid using bare except clauses, as they catch all exceptions and obscure the specific errors you're trying to handle.
try:
open('non_existent_file.txt')
except: # [bare-except]
file = None
try:
open('non_existent_file.txt')
except FileNotFoundError:
file = None
Try Except Raise
Rule ID: W0706
Description: Raising the same exception immediately after catching it (raise by itself) is redundant. Either raise a more detailed exception or remove the try-except block entirely.
try:
int('invalid')
except ValueError as e: # [try-except-raise]
raise
# Raise a more descriptive exception:
try:
int('invalid')
except ValueError as e:
raise TypeError("Input must be a valid integer") from e
Bad Indentation
Rule ID: W0311
Description: Improper indentation in Python is problematic, as it can lead to runtime errors. Ensure consistent indentation.
if input():
print('yes') # [bad-indentation]
if input():
print('yes')
Missing Final Newline
Rule ID: C0304
Description: The last line in a file is missing a newline
open("file.txt") # CRLF
close("file.txt") # End-of-file (EOF)
# [missing-final-newline]
open("file.txt")
close("file.txt")
# End-of-file (EOF)
Line Too Long
Rule ID: C0301
Description: A line is longer than a given number of characters
# +1: [line-too-long]
PLANETS = ["mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune"]
PLANETS = [
"mercury",
"venus",
"earth",
"mars",
"jupiter",
"saturn",
"uranus",
"neptune",
]
Mixed Line Endings
Rule ID: C0327
Description: There are mixed (LF and CRLF) newline signs in a file
read_file("file.txt") # CRLF
write_file("file.txt") # LF
# [mixed-line-endings]
read_file("file.txt") # CRLF
write_file("file.txt") # CRLF
Multiple Statements
Rule ID: C0321
Description: More than one statement is found on the same line
animals = ["cat", "dog", "parrot"]
if "cat" in animals: pass # [multiple-statements]
else:
print("no cats!")
animals = ["cat", "dog", "parrot"]
if "cat" in animals:
pass
else:
print("no cats!")
Too Many Lines
Rule ID: C0302
Description: A module has too many lines, reducing its readability
def calculate_sum(lst): # [too-many-lines]
sum = 0
for num in lst:
sum += num
return sum
def main():
print(calculate_sum([1, 2, 3]))
print(calculate_sum([10, 20, 30]))
Trailing Newlines
Rule ID: C0305
Description: There are trailing blank lines in a file
print("banana")
# The file ends with 2 empty lines # +1: [trailing-newlines]
print("banana")
Trailing Whitespace
Rule ID: C0303
Description: There is whitespace between the end of a line and the newline
print("Goodbye")
# [trailing-whitespace] #
print("Goodbye")
Unexpected Line Ending Format
Rule ID: C0328
Description: There is a different newline than expected
print("I'm eating breakfast!") # CRLF
print("I'm eating lunch!") # CRLF
# [unexpected-line-ending-format]
print("I'm eating breakfast!") # LF
print("I'm eating lunch!") # LF
Superfluous Parens
Rule ID: C0325
Description: A single item in parentheses follows an if, for, or other keyword
name = input()
age = input()
if (name == age): # [superfluous-parens]
pass
name = input()
age = input()
if name == age:
pass
Relative Beyond Top Level
Rule ID: E0402
Description: A relative import tries to access too many levels in the current package
from ................galaxy import Star # [relative-beyond-top-level]
from universe.galaxy import Star
Import Self
Rule ID: W0406
Description: A module is importing itself
from my_module import function_name # [import-self]
def function_name():
pass
Preferred Module
Rule ID: W0407
Description: A module imported has a preferred replacement module. They are configured in .pylintrc or through CLI arguments
import xmlrpc.client # [preferred-module]
import http.client
Reimported
Rule ID: W0404
Description: A module is reimported multiple times
import json
import json # [reimported]
import json
Deprecated Module
Rule ID: W0402
Description: Used a module marked as deprecated is imported
import imp # [deprecated-module]
import importlib
Wildcard Import
Rule ID: W0401
Description: This is a bad practice because it clutters namespace with unneeded modules, packages, variables, etc. Moreover, it takes time to load them too
from os import * # [wildcard-import]
import os
from os.path import join, dirname
Misplaced Future
Rule ID: W0410
Description: Python 2.5 and greater require future import to be the first non-docstring statement in the module. This message belongs to the imports checker
import time
from __future__ import division # [misplaced-future]
from __future__ import division
import time
Cyclic Import
Rule ID: R0401
Description: A cyclic import between two or more modules is detected
def add_numbers():
from .helper import add_two_numbers
return add_two_numbers() + 1
def add_two_numbers():
return 2
def add_three_numbers():
return add_two_numbers() + 1
Wrong Import Order
Rule ID: C0411
Description: PEP8 import order is not respected (standard imports first, then third-party libraries, then local imports)
import os
from . import tools
import sys # [wrong-import-order]
import os
import sys
from . import tools
Wrong Import Position
Rule ID: C0413
Description: Code and imports are mixed
import os
user_dir = os.getenv('USER')
import sys # [wrong-import-position]
import os
import sys
user_dir = os.getenv('USER')
Useless Import Alias
Rule ID: C0414
Description: An import alias is the same as the original package (e.g., using import numpy as numpy instead of import numpy as np)
import numpy as numpy # [useless-import-alias]
import numpy as np
Import Outside Toplevel
Rule ID: C0415
Description: An import statement is used anywhere other than the module top-level. Move this import to the top of the file
def get_version():
import platform
return platform.python_version()
import platform
def get_version():
return platform.python_version()
Ungrouped Imports
Rule ID: C0412
Description: Imports are not grouped by packages
import os
import sys
import json
import logging.config # [ungrouped-imports]
import os
import sys
import logging.config
import json
Multiple Imports
Rule ID: C0410
Description: Import statement importing multiple modules is detected
import os, sys # [multiple-imports]
import os
import sys
Logging Format Truncated
Rule ID: E1201
Description: A logging statement format string terminates before the end of a conversion specifier
import logging
logging.warning("Incorrect version: %", sys.version) # [logging-format-truncated]
import logging
logging.warning("Python version: %s", sys.version)
Logging Too Few Args
Rule ID: E1206
Description: A logging format string is given too few arguments
import logging
try:
function()
except Exception as e:
logging.error("%s error: %s", e) # [logging-too-few-args]
import logging
try:
function()
except Exception as e:
logging.error("%s error: %s", type(e), e)
Logging Too Many Args
Rule ID: E1205
Description: A logging format string is given too many arguments
import logging
try:
function()
except Exception as e:
logging.error("Error: %s", type(e), e) # [logging-too-many-args]
import logging
try:
function()
except Exception as e:
logging.error("%s error: %s", type(e), e)
Logging Unsupported Format
Rule ID: E1200
Description: An unsupported format character is used in a logging statement format string
import logging
logging.info("%s %y", "Hello", "World") # [logging-unsupported-format]
import logging
logging.info("%s %s", "Hello", "World")
Logging Format Interpolation
Rule ID: W1202
Description: A logging statement uses a call form of logging.<logging method>(format_string.format(...)). It’s better to pass the parameters directly to the logging function instead
logging.error("Version: {}".format(sys.version)) # [logging-format-interpolation]
logging.error("Version: %s", sys.version)
Logging Fstring Interpolation
Rule ID: W1203
Description: A logging statement has a call form of logging.<logging method>(f"..."). Use another type of string formatting instead. You can use different formatting but leave interpolation to the logging function by passing the parameters as arguments. If logging-format-interpolation is disabled then you can use str.format. If logging-not-lazy is disabled then you can use certain formatting as normal
import logging
import os
logging.warning(f"Current directory: {os.getcwd()}") # [logging-fstring-interpolation]
import logging
import os
logging.warning("Current directory: %s", os.getcwd())
Logging Not Lazy
Rule ID: W1201
Description: A logging statement has a call form of logging.<logging method>(format_string % (format_args...)). Use another type of string formatting instead. You can use different formatting but leave interpolation to the logging function by passing the parameters as arguments. If logging-fstring-interpolation is disabled then you can use fstring formatting. If logging-format-interpolation is disabled then you can use str.format
import logging
try:
connect_to_database()
except DatabaseError as e:
logging.error("Database connection failed: %s" % e) # [logging-not-lazy]
raise
import logging
try:
connect_to_database()
except DatabaseError as e:
logging.error("Database connection failed: %s", e)
raise
Unpacking In Except
Rule ID: E1603
Description: Implicit unpacking of exceptions is not supported in Python 3 Python3 will not allow implicit unpacking of exceptions in except clauses. See https://www.python.org/dev/peps/pep-3110/
try:
raise ValueError("An error occurred")
except ValueError, e: # Implicit unpacking (invalid in Python 3)
print(e)
try:
raise ValueError("An error occurred")
except ValueError as e: # Explicit unpacking (valid in Python 3)
print(e)
Import Star Module Level
Rule ID: E1609
Description: Import * only allowed at module level Used when the import star syntax is used somewhere else than the module level. This message can't be emitted when using Python >= 3.0
def my_function():
from math import * # Importing * inside a function (invalid)
from math import * # Importing * at the module level (valid)
Non Ascii Bytes Literal
Rule ID: E1610
Description: Non-ascii bytes literals not supported in 3.x Used when non-ascii bytes literals are found in a program. They are no longer supported in Python 3. This message can't be emitted when using Python >= 3.0
my_bytes = b"non-ASCII character: ü" # Non-ASCII byte literal (invalid in Python 3)
my_string = "non-ASCII character: ü" # Use string literals for non-ASCII text
Parameter Unpacking
Rule ID: E1602
Description: Parameter unpacking specified Used when parameter unpacking is specified for a function(Python 3 doesn't allow it)
exec 'print("Hello, world!")' # Python 2 style exec statement (invalid in Python 3)
exec('print("Hello, world!")') # Use exec() function in Python 3
Long Suffix
Rule ID: E1606
Description: Use of long suffix Used when "l" or "L" is used to mark a long integer. This will not work in Python 3, since int and long types have merged. This message can't be emitted when using Python >= 3.0
my_number = long(100) # long is not available in Python 3 (invalid)
my_number = int(100) # Use int in Python 3
Old Octal Literal
Rule ID: E1608
Description: Use of old octal literal Used when encountering the old octal syntax, removed in Python 3. To use the new syntax, prepend 0o on the number. This message can't be emitted when using Python >= 3.0
class MyClass: # Old-style class definition (Python 2)
pass
class MyClass(object): # New-style class definition (Python 3)
pass
Old Ne Operator
Rule ID: E1607
Description: Use of the <> operator Used when the deprecated <> operator is used instead of "!=". This is removed in Python 3. This message can't be emitted when using Python >= 3.0
if isinstance(my_var, basestring): # basestring is not available in Python 3
print("It's a string")
if isinstance(my_var, str): # Use str instead in Python 3
print("It's a string")
Backtick
Rule ID: E1605
Description: Use of the operator Used when the deprecated "" (backtick) operator is used instead of the str() function
if x <> y: # Python 2 style not-equal operator (invalid in Python 3)
print("x is not equal to y")
if x != y: # Use != operator in Python 3
print("x is not equal to y")
Old Raise Syntax
Rule ID: E1604
Description: Use raise ErrorClass(args) instead of raise ErrorClass, args. Used when the alternate raise syntax 'raise foo, bar' is used instead of 'raise foo(bar)'
print "my_var" # Backticks used for repr (invalid in Python 3)
print(repr(my_var)) # Use repr() function instead
Print Statement
Rule ID: E1601
Description: print statement used Used when a print statement is used (print is a function in Python 3)
print "Hello, world!" # Python 2 style print statement (invalid in Python 3)
print("Hello, world!") # Python 3 print function
Deprecated Types Field
Rule ID: W1652
Description: Accessing a deprecated fields on the types module Used when accessing a field on types that has been removed in Python 3
import types
my_type = types.SomeDeprecatedField # Deprecated field (invalid in Python 3)
# Use an alternative valid type or method in Python 3
Deprecated Itertools Function
Rule ID: W1651
Description: Accessing a deprecated function on the itertools module Used when accessing a function on itertools that has been removed in Python 3
import string # Let's assume "string" module is deprecated (hypothetical)
import string_new # Import updated or alternative module
Deprecated String Function
Rule ID: W1649
Description: Accessing a deprecated function on the string module Used when accessing a string function that has been deprecated in Python 3
import string
print(string.uppercase) # Deprecated in Python 3
import string
print(string.ascii_uppercase) # Use ascii_uppercase in Python 3
Deprecated Operator Function
Rule ID: W1657
Description: Accessing a removed attribute on the operator module Used when accessing a field on operator module that has been removed in Python 3
my_list = [1, 2, 3]
my_list.sort(lambda x: -x) # Deprecated way of sorting
my_list = sorted(my_list, reverse=True) # Use sorted() with reverse
Deprecated Sys Function
Rule ID: W1660
Description: Accessing a removed attribute on the sys module Used when accessing a field on sys module that has been removed in Python 3
my_iter = iter([1, 2, 3])
value = my_iter.next() # Use of ".next()" method (invalid in Python 3)
value = next(my_iter) # Use "next()" function instead
Deprecated Urllib Function
Rule ID: W1658
Description: Accessing a removed attribute on the urllib module Used when accessing a field on urllib module that has been removed or moved in Python 3
try:
risky_code()
except Exception, e: # Old-style exception handling (invalid in Python 3)
print(e)
try:
risky_code()
except Exception as e: # Use Python 3 style exception handling
print(e)
Xreadlines Attribute
Rule ID: W1659
Description: Accessing a removed xreadlines attribute Used when accessing the xreadlines() function on a file stream, removed in Python 3
import MyModule # Deprecated import
from mymodule import new_functionality # Use updated import
Metaclass Assignment
Rule ID: W1623
Description: Assigning to a class's metaclass attribute Used when a metaclass is specified by assigning to metaclass (Python 3 specifies the metaclass as a class statement argument)
my_list = [1, 2, 3]
print(my_list.has_key(2)) # Example: has_key() method is deprecated (invalid in Python 3)
print(2 in my_list) # Use "in" operator instead of has_key()
Next Method Called
Rule ID: W1622
Description: Called a next() method on an object Used when an object's next() method is called (Python 3 uses the next() built-in function)
Dict Iter Method
Rule ID: W1620
Description: Calling a dict.iter*() method Used for calls to dict.iterkeys(), itervalues() or iteritems() (Python 3 lacks these methods)
my_dict = {"a": 1, "b": 2}
for key in my_dict.iterkeys(): # iterkeys() is removed in Python 3
print(key)
my_dict = {"a": 1, "b": 2}
for key in my_dict.keys(): # Use keys() instead
print(key)
Dict View Method
Rule ID: W1621
Description: Calling a dict.view*() method Used for calls to dict.viewkeys(), viewvalues() or viewitems() (Python 3 lacks these methods)
my_dict = {"a": 1, "b": 2}
print(my_dict.viewkeys()) # viewkeys() is removed in Python 3
my_dict = {"a": 1, "b": 2}
print(my_dict.keys()) # Use keys() instead
Exception Message Attribute
Rule ID: W1645
Description: Exception.message removed in Python 3 Used when the message attribute is accessed on an Exception. Use str(exception) instead
try:
raise Exception("An error occurred")
except Exception as e:
print(e.message) # Exception.message is removed in Python 3
try:
raise Exception("An error occurred")
except Exception as e:
print(str(e)) # Use str() to access the error message
Eq Without Hash
Rule ID: W1641
Description: Implementing eq without also implementing hash Used when a class implements eq but not hash. In Python 2, objects get object.hash as the default implementation, in Python 3 objects get None as their default hash implementation if they also implement eq
class Fruit: # [eq-without-hash]
def __init__(self) -> None:
self.name = "apple"
def __eq__(self, other: object) -> bool:
return isinstance(other, Fruit) and other.name == self.name
class Fruit:
def __init__(self) -> None:
self.name = "apple"
def __eq__(self, other: object) -> bool:
return isinstance(other, Fruit) and other.name == self.name
def __hash__(self) -> int:
return hash(self.name)
Indexing Exception
Rule ID: W1624
Description: Indexing exceptions will not work on Python 3 Indexing exceptions will not work on Python 3. Use exception.args[index] instead
Bad Python3 Import
Rule ID: W1648
Description: Module moved in Python 3 Used when importing a module that no longer exists in Python 3
Raising String
Rule ID: W1625
Description: Raising a string exception Used when a string exception is raised. This will not work on Python 3
Standarderror Builtin
Rule ID: W1611
Description: StandardError built-in referenced Used when the StandardError built-in function is referenced (missing from Python 3)
try:
raise StandardError("An error occurred") # StandardError is removed in Python 3
except StandardError as e:
print(e)
try:
raise Exception("An error occurred") # Use Exception instead
except Exception as e:
print(e)
Comprehension Escape
Rule ID: W1662
Description: Using a variable that was bound inside a comprehension Emitted when using a variable, that was bound in a comprehension handler, outside of the comprehension itself. On Python 3 these variables will be deleted outside of the comprehension
Exception Escape
Rule ID: W1661
Description: Using an exception object that was bound by an except handler Emitted when using an exception, that was bound in an except handler, outside of the except handler. On Python 3 these exceptions will be deleted once they get out of the except handler
Deprecated Str Translate Call
Rule ID: W1650
Description: Using str.translate with deprecated deletechars parameters Used when using the deprecated deletechars parameters from str.translate. Use re.sub to remove the desired characters
Using Cmp Argument
Rule ID: W1640
Description: Using the cmp argument for list.sort / sorted Using the cmp argument for list.sort or the sorted builtin should be avoided, since it was removed in Python 3. Using either key or functools.cmp_to_key should be preferred
my_list = [3, 1, 2]
my_list.sort(cmp=lambda x, y: x - y) # cmp argument removed in Python 3
from functools import cmp_to_key
my_list = [3, 1, 2]
my_list.sort(key=cmp_to_key(lambda x, y: x - y)) # Use cmp_to_key in Python 3
Cmp Method
Rule ID: W1630
Description: cmp method defined Used when a cmp method is defined (method is not used by Python 3)
class MyClass:
def __cmp__(self, other): # __cmp__ is removed in Python 3
return cmp(self.value, other.value)
class MyClass:
def __lt__(self, other): # Define rich comparison methods instead
return self.value < other.value
Coerce Method
Rule ID: W1614
Description: coerce method defined Used when a coerce method is defined (method is not used by Python 3)
class MyClass:
def __coerce__(self, other):
return self.value, other.value # __coerce__ is not used in Python 3
class MyClass:
def __add__(self, other):
return self.value + other.value # Use other rich comparison methods
Delslice Method
Rule ID: W1615
Description: delslice method defined Used when a delslice method is defined (method is not used by Python 3)
class MyList(list):
def __delslice__(self, i, j): # __delslice__ is not used in Python 3
del self[i:j]
class MyList(list):
def __delitem__(self, key): # Use __delitem__ instead
del self[key]
Div Method
Rule ID: W1642
Description: div method defined Used when a div method is defined. Using truediv and setting__div__ = truediv should be preferred.(method is not used by Python 3)
class MyClass:
def __div__(self, other): # __div__ is removed in Python 3
return self.value / other.value
class MyClass:
def __truediv__(self, other): # Use __truediv__ instead
return self.value / other.value
Getslice Method
Rule ID: W1616
Description: getslice method defined Used when a getslice method is defined (method is not used by Python 3)
class MyList(list):
def __getslice__(self, i, j): # __getslice__ is not used in Python 3
return self[i:j]
class MyList(list):
def __getitem__(self, key): # Use __getitem__ instead
return self[key]
Hex Method
Rule ID: W1628
Description: hex method defined Used when a hex method is defined (method is not used by Python 3)
class MyClass:
def __hex__(self): # __hex__ is not used in Python 3
return hex(self.value)
class MyClass:
def __repr__(self): # Use __repr__ or custom method instead
return f'{self.value:#x}'
Idiv Method
Rule ID: W1643
Description: idiv method defined Used when an idiv method is defined. Using itruediv and setting__idiv__ = itruediv should be preferred.(method is not used by Python 3)
class MyClass:
def __idiv__(self, other): # __idiv__ is removed in Python 3
self.value /= other.value
class MyClass:
def __itruediv__(self, other): # Use __itruediv__ instead
self.value /= other.value
Nonzero Method
Rule ID: W1629
Description: nonzero method defined Used when a nonzero method is defined (method is not used by Python 3)
class MyClass:
def __nonzero__(self): # __nonzero__ is removed in Python 3
return bool(self.value)
class MyClass:
def __bool__(self): # Use __bool__ instead
return bool(self.value)
Oct Method
Rule ID: W1627
Description: oct method defined Used when an oct method is defined (method is not used by Python 3)
class MyClass:
def __oct__(self): # __oct__ is not used in Python 3
return oct(self.value)
class MyClass:
def __repr__(self): # Use __repr__ or custom method instead
return f'{self.value:#o}'
Rdiv Method
Rule ID: W1644
Description: rdiv method defined Used when a rdiv method is defined. Using rtruediv and setting__rdiv__ = rtruediv should be preferred.(method is not used by Python 3)
class MyClass:
def __rdiv__(self, other): # __rdiv__ is removed in Python 3
return other.value / self.value
class MyClass:
def __rtruediv__(self, other): # Use __rtruediv__ instead
return other.value / self.value
Setslice Method
Rule ID: W1617
Description: setslice method defined Used when a setslice method is defined (method is not used by Python 3)
class MyList(list):
def __setslice__(self, i, j, sequence): # __setslice__ is removed in Python 3
self[i:j] = sequence
class MyList(list):
def __setitem__(self, key, value): # Use __setitem__ instead
self[key] = value
Apply Builtin
Rule ID: W1601
Description: apply built-in referenced Used when the apply built-in function is referenced (missing from Python 3)
apply(func, (arg1, arg2)) # apply is removed in Python 3
func(arg1, arg2) # Direct function call is valid in Python 3
Basestring Builtin
Rule ID: W1602
Description: basestring built-in referenced Used when the basestring built-in function is referenced (missing from Python 3)
if isinstance(my_var, basestring): # basestring is removed in Python 3
print("It's a string")
if isinstance(my_var, str): # Use str in Python 3
print("It's a string")
Buffer Builtin
Rule ID: W1603
Description: buffer built-in referenced Used when the buffer built-in function is referenced (missing from Python 3)
my_buf = buffer(my_var) # buffer is removed in Python 3
# buffer functionality is handled differently in Python 3, use memoryview or bytearray
my_buf = memoryview(my_var)
Cmp Builtin
Rule ID: W1604
Description: cmp built-in referenced Used when the cmp built-in function is referenced (missing from Python 3)
result = cmp(a, b) # cmp is removed in Python 3
result = (a > b) - (a < b) # Use comparison operators in Python 3
Coerce Builtin
Rule ID: W1605
Description: coerce built-in referenced Used when the coerce built-in function is referenced (missing from Python 3)
result = coerce(a, b) # coerce is removed in Python 3
Dict Items Not Iterating
Rule ID: W1654
Description: dict.items referenced when not iterating Used when dict.items is referenced in a non-iterating context (returns an iterator in Python 3)
my_items = my_dict.items() # This returns a view object in Python 3
my_items = list(my_dict.items()) # Convert to list for Python 3 behavior
Dict Keys Not Iterating
Rule ID: W1655
Description: dict.keys referenced when not iterating Used when dict.keys is referenced in a non-iterating context (returns an iterator in Python 3)
my_keys = my_dict.keys() # This returns a view object in Python 3
my_keys = list(my_dict.keys()) # Convert to list for Python 3 behavior
Dict Values Not Iterating
Rule ID: W1656
Description: dict.values referenced when not iterating Used when dict.values is referenced in a non-iterating context (returns an iterator in Python 3)
my_values = my_dict.values() # This returns a view object in Python 3
my_values = list(my_dict.values()) # Convert to list for Python 3 behavior
Old Division
Rule ID: W1619
Description: division w/o future statement Used for non-floor division w/o a float literal or from future import division (Python 3 returns a float for int division unconditionally)
result = 5 / 2 # Returns integer in Python 2 (invalid in Python 3)
Execfile Builtin
Rule ID: W1606
Description: execfile built-in referenced Used when the execfile built-in function is referenced (missing from Python 3)
execfile('script.py') # execfile is removed in Python 3
with open('script.py') as file:
exec(file.read()) # Use open and exec in Python 3
File Builtin
Rule ID: W1607
Description: file built-in referenced Used when the file built-in function is referenced (missing from Python 3)
file_object = file('example.txt', 'r') # file is removed in Python 3
file_object = open('example.txt', 'r') # Use open in Python 3
Filter Builtin Not Iterating
Rule ID: W1639
Description: filter built-in referenced when not iterating Used when the filter built-in is referenced in a non-iterating context (returns an iterator in Python 3)
result = filter(lambda x: x > 0, my_list) # Returns iterator in Python 3, no list (invalid)
result = list(filter(lambda x: x > 0, my_list)) # Convert to list for Python 3
No Absolute Import
Rule ID: W1618
Description: import missing from __future__ import absolute_import Used when an import is not accompanied by from future import absolute_import (default behaviour in Python 3)
import mymodule # Without future import in Python 2
from __future__ import absolute_import # Use absolute imports in Python 3
import mymodule
Input Builtin
Rule ID: W1632
Description: input built-in referenced Used when the input built-in is referenced (backwards-incompatible semantics in Python 3)
user_input = input("Enter something: ") # Works differently in Python 2
Intern Builtin
Rule ID: W1634
Description: intern built-in referenced Used when the intern built-in is referenced (Moved to sys.intern in Python 3)
my_interned = intern("example") # intern is moved in Python 3
import sys
my_interned = sys.intern("example") # Use sys.intern() in Python 3
Long Builtin
Rule ID: W1608
Description: long built-in referenced Used when the long built-in function is referenced (missing from Python 3)
my_num = long(123456789) # long is removed in Python 3
my_num = int(123456789) # Use int in Python 3
Map Builtin Not Iterating
Rule ID: W1636
Description: map built-in referenced when not iterating Used when the map built-in is referenced in a non-iterating context (returns an iterator in Python 3)
result = map(lambda x: x * 2, my_list) # Returns an iterator in Python 3
result = list(map(lambda x: x * 2, my_list)) # Convert to list in Python 3
Next Method Defined
Rule ID: W1653
Description: next method defined Used when a next method is defined that would be an iterator in Python 2 but is treated as a normal function in Python 3
class MyClass:
def next(self): # Treated differently in Python 3
return 42
class MyClass:
def __next__(self): # Use __next__ in Python 3
return 42
Invalid Str Codec
Rule ID: W1646
Description: non-text encoding used in str.decode Used when using str.encode or str.decode with a non-text encoding. Use codecs module to handle arbitrary codecs
encoded_str = "hello".encode("hex") # Non-text codec is removed in Python 3
import codecs
encoded_str = codecs.encode("hello", "hex") # Use codecs module in Python 3
Range Builtin Not Iterating
Rule ID: W1638
Description: range built-in referenced when not iterating Used when the range built-in is referenced in a non-iterating context (returns a range in Python 3)
result = range(10) # Returns an iterator in Python 3
result = list(range(10)) # Convert to list in Python 3
Raw_input Builtin
Rule ID: W1609
Description: raw_input built-in referenced Used when the raw_input built-in function is referenced (missing from Python 3)
user_input = raw_input("Enter something: ") # raw_input is removed in Python 3
user_input = input("Enter something: ") # Use input in Python 3
Reduce Builtin
Rule ID: W1610
Description: reduce built-in referenced Used when the reduce built-in function is referenced (missing from Python 3)
result = reduce(lambda x, y: x + y, my_list) # reduce is removed from built-ins in Python 3
from functools import reduce # Import from functools in Python 3
result = reduce(lambda x, y: x + y, my_list)
Reload Builtin
Rule ID: W1626
Description: reload built-in referenced Used when the reload built-in function is referenced (missing from Python 3). You can use instead imp.reload or importlib.reload
reload(mymodule) # reload is removed in Python 3
from importlib import reload # Use importlib.reload in Python 3
reload(mymodule)
Round Builtin
Rule ID: W1633
Description: round built-in referenced Used when the round built-in is referenced (backwards-incompatible semantics in Python 3)
rounded = round(5.675, 2) # Different behavior in Python 3 for rounding
Sys Max Int
Rule ID: W1647
Description: sys.maxint removed in Python 3 Used when accessing sys.maxint. Use sys.maxsize instead
import sys
print(sys.maxint) # maxint is removed in Python 3
import sys
print(sys.maxsize) # Use sys.maxsize in Python 3
Unichr Builtin
Rule ID: W1635
Description: unichr built-in referenced Used when the unichr built-in is referenced (Use chr in Python 3)
char = unichr(97) # unichr is removed in Python 3
char = chr(97) # Use chr in Python 3
Unicode Builtin
Rule ID: W1612
Description: unicode built-in referenced Used when the unicode built-in function is referenced (missing from Python 3)
my_str = unicode("example") # unicode is removed in Python 3
my_str = str("example") # Use str in Python 3
Xrange Builtin
Rule ID: W1613
Description: xrange built-in referenced Used when the xrange built-in function is referenced (missing from Python 3)
for i in xrange(10): # xrange is removed in Python 3
print(i)
for i in range(10): # Use range in Python 3
print(i)
Zip Builtin Not Iterating
Rule ID: W1637
Description: zip built-in referenced when not iterating Used when the zip built-in is referenced in a non-iterating context (returns an iterator in Python 3)
result = zip(list1, list2) # Returns an iterator in Python 3
result = list(zip(list1, list2)) # Convert to list in Python 3
Simplifiable Condition
Rule ID: R1726
Description: A boolean condition is able to be simplified
def has_bananas(bananas) -> bool:
return bool(bananas or False) # [simplifiable-condition]
def has_bananas(bananas) -> bool:
return bool(bananas)
Condition Evals To Constant
Rule ID: R1727
Description: A boolean condition can be simplified to a constant value
def is_a_vegetable(vegetable):
return bool(vegetable in {"carrot", "broccoli"} or True) # [condition-evals-to-constant]
def is_a_vegetable(vegetable):
return vegetable in {"carrot", "broccoli"}
Simplify Boolean Expression
Rule ID: R1709
Description: Emitted when redundant pre-python 2.5 ternary syntax is used
def has_grapes(grapes, oranges=None) -> bool:
return oranges and False or grapes # [simplify-boolean-expression]
def has_grapes(grapes, oranges=None) -> bool:
return grapes
Consider Using In
Rule ID: R1714
Description: To check if a variable is equal to one of many values, combine the values into a tuple and check if the variable is contained in it instead of checking for equality against each of the values. This is faster and less verbose
def fruit_is_yellow(fruit):
# +1: [consider-using-in]
return fruit == "banana" or fruit == "lemon" or fruit == "pineapple"
def fruit_is_yellow(fruit):
return fruit in {"banana", "lemon", "pineapple"}
Consider Merging Isinstance
Rule ID: R1701
Description: Multiple consecutive isinstance calls can be merged into one
from typing import Any
def is_string(value: Any) -> bool:
# +1: [consider-merging-isinstance]
return isinstance(value, str) or isinstance(value, bytes)
from typing import Any
def is_string(value: Any) -> bool:
return isinstance(value, (str, bytes))
Super With Arguments
Rule ID: R1725
Description: Consider using Python 3 style super() without arguments. Emitted when calling the super() builtin with the current class and instance. On Python 3 these arguments are the default and they can be omitted
class Fruit:
pass
class Banana(Fruit):
def __init__(self):
super(Banana, self).__init__() # [super-with-arguments]
class Fruit:
pass
class Banana(Fruit):
def __init__(self):
super().__init__()
Consider Using Dict Comprehension
Rule ID: R1717
Description: Emitted when detect the creation of a dictionary using the dict() callable and a transient list. Although there is nothing syntactically wrong with this code, it is hard to read and can be simplified to a dict comprehension. Also it is faster since you don't need to create another transient list
fruits = ["apple", "pear", "banana"]
# +1: [consider-using-dict-comprehension]
FRUIT_LENGTHS = dict([(fruit, len(fruit)) for fruit in fruits])
fruits = ["apple", "pear", "banana"]
FRUIT_LENGTHS = {fruit: len(fruit) for fruit in fruits}
Consider Using Set Comprehension
Rule ID: R1718
Description: Although there is nothing syntactically wrong with this code, it is hard to read and can be simplified to a set comprehension. Also, it is faster since you don't need to create another transient list
fruits = ["apple", "banana", "banana", "pear", "kiwi", "kiwi"]
# +1: [consider-using-set-comprehension]
UNIQUE_FRUITS = set([fruit for fruit in fruits if fruit.startswith('k')])
fruits = ["apple", "banana", "banana", "pear", "kiwi", "kiwi"]
UNIQUE_FRUITS = {fruit for fruit in fruits if fruit.startswith('k')}
Consider Using Get
Rule ID: R1715
Description: Using the builtin dict.get for key lookups is preferred to avoid KeyError exceptions
knights = {"Arthur": "the brave", "Lancelot": "the noble"}
if "Arthur" in knights: # [consider-using-get]
DESCRIPTION = knights["Arthur"]
else:
DESCRIPTION = ""
knights = {"Arthur": "the brave", "Lancelot": "the noble"}
description = knights.get("Arthur", "")
Consider Using Join
Rule ID: R1713
Description: Using str.join(sequence) is faster, uses less memory and increases readability compared to for-loop iteration
def numbers_to_string(numbers):
formatted_number = ''
for number in numbers:
formatted_number += str(number) # [consider-using-join]
return formatted_number
print(numbers_to_string([1, 2, 3]))
print(''.join(map(str, [1, 2, 3])))
Consider Using Sys Exit
Rule ID: R1722
Description: Instead of using exit() or quit(), consider using the sys.exit()
if __name__ == '__main__':
age = input('Enter your age: ')
print(f'Your age is {age}')
exit(0) # [consider-using-sys-exit]
import sys
if __name__ == '__main__':
age = input('Enter your age: ')
print(f'Your age is {age}')
sys.exit(0)
Consider Using Ternary
Rule ID: R1706
Description: One of known pre-python 2.5 ternary syntax is used
a, b = 5, 10
result = a > b and a or b # [consider-using-ternary]
a, b = 5, 10
result = a if a > b else b
Consider Swap Variables
Rule ID: R1712
Description: You do not have to use a temporary variable in order to swap variables. Using tuple unpacking to directly swap variables makes the intention more clear
x = 3
y = 5
temp = x # [consider-swap-variables]
x = y
y = temp
x = 3
y = 5
x, y = y, x
Trailing Comma Tuple
Rule ID: R1707
Description: In Python, a tuple is created by the comma symbol, not by parentheses. Using a trailing comma can create unwanted tuples, so always use parentheses for clarity.
DIRECTIONS = 'left', 'right', 'up', 'down', # [trailing-comma-tuple]
DIRECTIONS = ('left', 'right', 'up', 'down')
Stop Iteration Return
Rule ID: R1708
Description: According to PEP479, raising StopIteration in a generator can lead to bugs. It's better to rely on the natural termination of a generator.
def color_generator():
for color in ['red', 'blue']:
yield color
raise StopIteration # [stop-iteration-return]
def color_generator():
"""No need for an explicit return in this simple case."""
for color in ['red', 'blue']:
yield color
Inconsistent Return Statements
Rule ID: R1710
Description: All return statements in a function should be consistent. If some return a value, others should explicitly return None.
def find_maximum(value: int) -> int | None: # [inconsistent-return-statements]
if value > 0:
return value
def find_maximum(value: int) -> int | None:
if value > 0:
return value
return None
Redefined Argument From Local
Rule ID: R1704
Description: A local name is redefining an argument, which might suggest a potential error. This is relevant for iterations and similar constructs.
def calculate(radius=5.5):
# +1: [redefined-argument-from-local]
for radius, area in [(3, 28.27), (5, 78.54)]:
print(radius, area)
def calculate(radius=5.5):
for r, area in [(3, 28.27), (5, 78.54)]:
print(radius, r, area)
Consider Using In
Rule ID: R1716
Description: When testing for membership in a collection inside a loop, it is preferable to structure your loop to avoid deeply nested blocks. Using continue statements can lead to complex control flows, which is harder to read and maintain.
for fruit in fruits:
if fruit in UNIQUE_FRUITS: # [consider-using-in]
print(fruit)
continue
print(fruit)
for fruit in fruits:
if fruit in UNIQUE_FRUITS:
print(fruit)
continue
else:
print(fruit)
Simplifiable If Expression
Rule ID: R1719
Description: An if expression can be replaced with bool(test)
VEHICLES = ["car", "boat", "rocket", "this example"]
def is_vehicle(an_object):
return True if an_object in VEHICLES else False # [simplifiable-if-expression]
def is_not_vehicle(an_object):
return False if an_object in VEHICLES else True # [simplifiable-if-expression]
VEHICLES = ["car", "boat", "rocket", "this example"]
def is_vehicle(an_object):
return an_object in VEHICLES
def is_not_vehicle(an_object):
return an_object not in VEHICLES
Simplifiable If Statement
Rule ID: R1703
Description: An if statement can be replaced with bool(test)
WILD_ANIMALS = ["lion", "tiger", "elephant", "this example"]
def is_wild_animal(an_object):
# +1: [simplifiable-if-statement]
if isinstance(an_object, Animal) and an_object in WILD_ANIMALS:
is_wild = True
else:
is_wild = False
return is_wild
WILD_ANIMALS = ["lion", "tiger", "elephant", "this example"]
def is_wild_animal(an_object):
is_wild = isinstance(an_object, Animal) and an_object.name in WILD_ANIMALS
return is_wild
Too Many Nested Blocks
Rule ID: R1702
Description: A function or a method has too many nested blocks. This makes the code less understandable and maintainable. Maximum number of nested blocks for function / method body is 5 by default
def validate_colors(colors):
if len(colors) > 2: # [too-many-nested-blocks]
if "red" in colors:
if "blue" in colors:
count = colors["blue"]
if count % 2:
if "green" in colors:
if count == 2:
return True
return False
def validate_colors(colors):
if len(colors) > 2 and "red" in colors and "blue" in colors:
count = colors["blue"]
if count % 2 and "green" in colors and count == 2:
return True
return False
No Else Break
Rule ID: R1723
Description: Used in order to highlight an unnecessary block of code following an if containing a break statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a break statement
def next_ten_numbers(iterator):
for i, item in enumerate(iterator):
if i == 10: # [no-else-break]
break
else:
yield item
def next_ten_numbers(iterator):
for i, item in enumerate(iterator):
if i == 10:
break
yield item
No Else Continue
Rule ID: R1724
Description: Used in order to highlight an unnecessary block of code following an if containing a continue statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a continue statement
def odd_number_under(n: int):
for i in range(n):
if i % 2 == 0: # [no-else-continue]
continue
else:
yield i
def odd_number_under(n: int):
for i in range(n):
if i % 2 == 0:
continue
yield i
No Else Raise
Rule ID: R1720
Description: Used in order to highlight an unnecessary block of code following an if containing a raise statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a raise statement
def float_sum(a: float, b: float) -> float:
if not (isinstance(a, float) and isinstance(b, float)): # [no-else-raise]
raise ValueError("Function supports only float parameters.")
else:
return a + b
def float_sum(a: float, b: float) -> float:
if not (isinstance(a, float) and isinstance(b, float)):
raise ValueError("Function supports only float parameters.")
return a + b
No Else Return
Rule ID: R1705
Description: Unnecessary else after return. Used in order to highlight an unnecessary block of code following an if containing a return statement. As such, it will warn when it encounters an else following a chain of ifs, all of them containing a return statement
def compare_strings(a: str, b: str) -> int:
if a == b: # [no-else-return]
return 0
elif a < b:
return -1
else:
return 1
def compare_strings(a: str, b: str) -> int:
if a == b:
return 0
if a < b:
return -1
return 1
Unnecessary Comprehension
Rule ID: R1721
Description: Instead of using an identity comprehension, consider using the list, dict or set constructor. It is faster and simpler
ANIMALS = ["cat", "dog", "fish", "this example"]
UNIQUE_ANIMALS = {animal for animal in ANIMALS} # [unnecessary-comprehension]
ANIMALS = ["cat", "dog", "fish", "this example"]
UNIQUE_ANIMALS = set(ANIMALS)
Useless Return
Rule ID: R1711
Description: A function is returning a value that is unnecessary. This could be useful to identify functions that should simply be called for their side effects
import os
def show_current_directory():
return os.getcwd() # [useless-return]
import os
def show_current_directory():
print(os.getcwd())
Unneeded Not
Rule ID: C0113
Description: A boolean expression contains an unnecessary negation. Removing it increases readability and avoids potential confusion.
if not (x == y): # [unneeded-not]
if x != y:
Consider Iterating Dictionary
Rule ID: C0201
Description: When iterating over a dictionary, there's no need to explicitly use .keys(). Just iterate through the dictionary itself for simplicity.
CARS = {'Toyota': 3, 'Honda': 5, 'Ford': 2}
for car in CARS.keys(): # [consider-iterating-dictionary]
print(car)
CARS = {'Toyota': 3, 'Honda': 5, 'Ford': 2}
for car in CARS:
print(car)
Fuzzy Comparison
Rule ID: C0200
Description: Fuzzy comparisons can produce unexpected results. Use a mathematical equivalence instead.
def is_square(n):
return n == n ** 0.5 # [fuzzy-comparison]
def is_cube(n):
return n == n ** (1/3) # [fuzzy-comparison]
def is_square(n):
return n ** 2 == n
def is_cube(n):
return n ** (1/3) == n
Len As Condition
Rule ID: C1801
Description: Pylint detects that len(sequence) is being used without explicit comparison inside a condition to determine if a sequence is empty. Instead of coercing the length to a boolean, either rely on the fact that empty sequences are false or compare the length against a scalar.
if len(sequence) == 0: # [unnecessary-length-check]
if not sequence: # More Pythonic way to check for an empty sequence
Invalid Envvar Value
Rule ID: E1507
Description: Env manipulation functions support only string type arguments.
import os
os.getenv(True) # [invalid-envvar-value]
import os
os.getenv("TRUE")
Bad Open Mode
Rule ID: W1501
Description: Python supports r, w, a[, x] modes with b, +, and U (only with r) options.
def open_and_get_content(file_path):
with open(file_path, "abc") as file: # [bad-open-mode]
return file.read()
def open_and_get_content(file_path):
with open(file_path, "r") as file:
return file.read()
Invalid Envvar Default
Rule ID: W1508
Description: Env manipulation functions return None or str values. Supplying anything different as a default may cause bugs. See https://docs.python.org/3/library/os.html#os.getenv for more details.
import os
env = os.getenv("API_URL", []) # [invalid-envvar-default]
import os
env = os.getenv("API_URL", "default_value")
Redundant Unittest Assert
Rule ID: W1503
Description: The first argument of assertTrue and assertFalse is a condition. If a constant is passed as a parameter, that condition will always be true. In this case, a warning should be emitted.
import unittest
class DummyTestCase(unittest.TestCase):
def test_dummy(self):
self.assertTrue(1) # [redundant-unittest-assert]
import unittest
class DummyTestCase(unittest.TestCase):
def test_dummy(self):
actual = "result"
self.assertEqual(actual, "expected")
Shallow Copy Environ
Rule ID: W1507
Description: os.environ is not a dict object but a proxy object, so a deep copy may still affect the original object. See https://bugs.python.org/issue15373 for reference.
import copy
import os
copied_env = copy.deepcopy(os.environ) # [deep-copy-environ]
import os
copied_env = os.environ.copy()
Boolean Datetime
Rule ID: W1502
Description: Using datetime.date in a boolean context can hide subtle bugs when the date they represent is a specific value like the epoch. This behavior was fixed in Python 3.5. See http://bugs.python.org/issue13936 for reference. It can't be emitted when using Python >= 3.5.
import datetime
if not datetime.date(2000, 1, 1): # [boolean-datetime]
print("Date is valid.")
import datetime
today = datetime.date.today()
if today >= datetime.date(2000, 1, 1):
print("Date is valid.")
Deprecated Method
Rule ID: W1505
Description: The method is marked as deprecated and will be removed in a future version of Python. Consider looking for an alternative in the documentation.
old_function() # [deprecated-function]
new_function()
Subprocess Popen Preexec Fn
Rule ID: W1509
Description: The preexec_fn parameter is not safe to use in the presence of threads in your application. The child process could deadlock before exec is called. If you must use it, keep it trivial! Minimize the number of libraries you call into.
import subprocess
def do_nothing():
pass
subprocess.Popen(preexec_fn=do_nothing) # [subprocess-popen-preexec-fn]
import subprocess
subprocess.Popen()
Subprocess Run Check
Rule ID: W1510
Description: The check parameter should always be used with explicitly set check keyword to make clear what the error-handling behavior is.
import subprocess
proc = subprocess.run(["echo", "Hello World"]) # [subprocess-run-check]
import subprocess
proc = subprocess.run(["echo", "Hello World"], check=False)
Bad Thread Instantiation
Rule ID: W1506
Description: The warning is emitted when a threading.Thread class is instantiated without the target function being passed. By default, the first parameter is the group param, not the target param.
import threading
def thread_function():
print("Thread running")
thread = threading.Thread(target=thread_function, args=None) # [bad-thread-instantiation]
thread.start()
import threading
def thread_function(n):
print(n)
thread = threading.Thread(target=thread_function, args=(5,))
thread.start()
Bad String Format Type
Rule ID: E1307
Description: A type required by the format string is not suitable for the actual argument type.
print("%s" % 10) # [bad-string-format-type]
print("%s" % "10")
Format Needs Mapping
Rule ID: E1303
Description: A format string that uses named conversion specifiers is used with an argument that is not a mapping
print("%(a)s %(b)s" % ["foo", "bar"]) # [format-needs-mapping]
print("%(a)s %(b)s" % {"a": "foo", "b": "bar"})
Truncated Format String
Rule ID: E1301
Description: A format string terminates before the end of a conversion specifier
NUM_1 = 5
print("value %2" % NUM_1) # [truncated-format-string]
NUM_1 = 5
print(f"value {NUM_1}")
Missing Format String Key
Rule ID: E1304
Description: A format string that uses named conversion specifiers is used with a dictionary that doesn't contain all the keys required by the format string
# +1: [missing-format-string-key]
vegetable_prices = """
Carrot: %(carrot_price)d ¤
Potato: %(potato_price)d ¤
""" % {
"carrot_price": 30
}
vegetable_prices = """
Carrot: %(carrot_price)d ¤
Potato: %(potato_price)d ¤
""" % {
"carrot_price": 30,
"potato_price": 45,
}
Mixed Format String
Rule ID: E1302
Description: A format string contains both named and unnamed conversion specifiers. This is also used when a named conversion specifier contains * for the minimum field width and/or precision
print("a=%(a)d, b=%d" % (2, 3)) # [mixed-format-string]
print("a=%(a)d, b=%(b)d" % {"a": 2, "b": 3})
Too Few Format Args
Rule ID: E1306
Description: A format string that uses unnamed conversion specifiers is given too few arguments
print("The weather is {0}, and tomorrow will be {1}".format("sunny")) # [too-few-format-args]
print("The weather is {0}, and tomorrow will be {1}".format("sunny", "cloudy"))
Bad Str Strip Call
Rule ID: E1310
Description: The argument to a str.{l,r,}strip call contains a duplicate character
"Python Programming".strip("Pyt") # [bad-str-strip-call]
# >>> 'hon Programming'
"Python Programming".strip("Pytho")
# >>> 'n Programming'
Too Many Format Args
Rule ID: E1305
Description: A format string that uses unnamed conversion specifiers is given too many arguments
# +1: [too-many-format-args]
print("This year is {0}, next year will be {1}".format("2023", "2024", "2025"))
print("This year is {0}, next year will be {1}".format("2023", "2024"))
Bad Format Character
Rule ID: E1300
Description: An unsupported format character is used in a format string
print("%d %q" % (10, 20)) # [bad-format-character]
print("%d %d" % (10, 20))
Anomalous Unicode Escape In String
Rule ID: W1402
Description: An escape like \u is encountered in a byte string where it has no effect
print(b"%𐍈" % b"data") # [anomalous-unicode-escape-in-string]
print(b"\x50\u2341" % b"data")
Anomalous Backslash In String
Rule ID: W1401
Description: A backslash is in a literal string but not as an escape
path = "c:\folder" # [syntax-error]
path = "c:\\folder"
Duplicate String Formatting Argument
Rule ID: W1308
Description: We detect that a string formatting is repeating an argument instead of using named string arguments
# pylint: disable=missing-docstring, consider-using-f-string
SKY = "sky ☁️"
STAR = "star ✨"
# +1: [duplicate-string-formatting-argument,duplicate-string-formatting-argument]
CONST = """
Twinkle twinkle little {}, {}
How I wonder what you {}, {}
Up above the world so {}, {}
Like a diamond in the {}, {}!
""".format(
STAR,
STAR,
SKY,
SKY,
SKY,
STAR,
STAR
)
# pylint: disable=missing-docstring, consider-using-f-string
SKY = "sky ☁️"
STAR = "star ✨"
CONST = """
Twinkle twinkle little {star}, {star}
How I wonder what you {sky}, {sky}
Up above the world so {sky}, {sky}
Like a diamond in the {star}, {star}!
""".format(
star=STAR, sky=SKY
)
Format Combined Specification
Rule ID: W1305
Description: A PEP 3101 format string contains both automatic field numbering (e.g. {}) and manual field specification (e.g. {0})
print("{} {2}".format("apple", "banana")) # [format-combined-specification]
print("{0} {1}".format("apple", "banana"))
Bad Format String Key
Rule ID: W1300
Description: A format string that uses named conversion specifiers is used with a dictionary whose keys are not all strings
print("%(key1)s" % {"key1": "value", 100: "num"}) # [bad-format-string-key]
print("%(key1)s, %(key2)s" % {"key1": "value", "key2": "num"})
Implicit Str Concat
Rule ID: W1404
Description: Implicit string concatenation found. String literals are implicitly concatenated in a literal iterable definition: maybe a comma is missing?
with open("file.txt" "w") as f: # [implicit-str-concat]
f.write("data")
with open("file.txt", "w") as f:
f.write("data")
Bad Format String
Rule ID: W1302
Description: A PEP 3101 format string is invalid
print("{b[0] * b[1]}".format(b=[2, 3])) # [bad-format-string]
print("{b[0]} * {b[1]}".format(b=[2, 3]))
Missing Format Attribute
Rule ID: W1306
Description: A PEP 3101 format string uses an attribute specifier (e.g., {0.real}), but the argument passed for formatting doesn't have that attribute
print("{0.imag}".format(5)) # [missing-format-attribute]
print("{0.imag}".format(5j))
Missing Format Argument Key
Rule ID: W1303
Description: A PEP 3101 format string that uses named fields doesn't receive one or more required keywords
print("My pet is a {type} named {name}".format(type="dog")) # [missing-format-argument-key]
print("My pet is a {type} named {name}".format(type="dog", name="Buddy"))
Inconsistent Quotes
Rule ID: W1405
Description: Quote delimiter is inconsistent with the rest of the file. Quote delimiters are not used consistently throughout a module (with allowances made for avoiding unnecessary escaping)
import time
print('Current time: ', time.strftime("%H:%M:%S")) # [inconsistent-quotes]
import time
print("Current time: ", time.strftime("%H:%M:%S"))
Unused Format String Argument
Rule ID: W1304
Description: A PEP 3101 format string that uses named fields is used with an argument that is not required by the format string
print("{a} {b}".format(a=4, b=5, c=6)) # [unused-format-string-argument]
print("{a} {b} {c}".format(a=4, b=5, c=6))
Unused Format String Key
Rule ID: W1301
Description: A format string that uses named conversion specifiers is used with a dictionary that contains keys not required by the format string
"The lazy %(animal)s sleeps all day." % {
"animal": "cat",
"activity": "jumps",
}
# -4: [unused-format-string-key]
"The lazy %(animal)s %(activity)s all day." % {
"animal": "cat",
"activity": "jumps",
}
F String Without Interpolation
Rule ID: W1309
Description: Using an f-string that does not have any interpolated variables. This occurs when an f-string is used, but it can be a normal string or a potential bug in the code
x = 3
y = 4
print(f"x multiplied by y equals x * y") # [f-string-without-interpolation]
x = 3
y = 4
print(f"{x} multiplied by {y} equals {x * y}")
Invalid Format Index
Rule ID: W1307
Description: A PEP 3101 format string uses a lookup specifier ({a[2]}), but the argument passed for formatting doesn't contain or doesn't have that key as an attribute
fruits = ["mango"]
print('The third fruit is {fruits[2]}'.format(fruits=fruits)) # [invalid-format-index]
fruits = ["mango", "apple", "banana"]
print("The third fruit is {fruits[2]}".format(fruits=fruits))
Unsupported Assignment Operation
Rule ID: E1137
Description: Emitted when an object does not support item assignment (i.e., doesn't define the __setitem__ method)
def get_colors(colors):
for color in colors:
print(color)
get_colors(["blue"])[0] = "red" # [unsupported-assignment-operation]
def get_colors(colors):
for color in colors:
print(color)
return []
get_colors(["blue"])[0] = "red"
Unsupported Delete Operation
Rule ID: E1138
Description: Emitted when an object does not support item deletion (i.e. doesn't define __delitem__ method)
VEGETABLES = ("carrot", "tomato", "pepper")
del VEGETABLES[1] # [unsupported-delete-operation]
VEGETABLES = ["carrot", "tomato", "pepper"]
del VEGETABLES[1]
Invalid Unary Operand Type
Rule ID: E1130
Description: Emitted when a unary operand is used on an object which does not support this type of operation
cookies = 5
missing_cookies = str
cookies = -missing_cookies # [invalid-unary-operand-type]
cookies = 5
missing_cookies = 2
cookies -= missing_cookies
Unsupported Binary Operation
Rule ID: E1131
Description: Emitted when a binary arithmetic operation between two operands is not supported
color = "blue" & None # [unsupported-binary-operation]
shape = {} | None # [unsupported-binary-operation]
masked = 0b110101 & 0b101001
result = 0xABCD | 0x1234
No Member
Rule ID: E1101
Description: A variable is accessed for a non-existent member
from os import path
location = path(".").drives # [no-member]
class Dog:
def bark(self):
print("Bark")
Dog().run() # [no-member]
from os import path
location = path.abspath(".")
class Dog:
def bark(self):
print("Bark")
Dog().bark()
Not Callable
Rule ID: E1102
Description: An object being called has been inferred to a non callable object
AGE = 25
print(AGE()) # [not-callable]
AGE = 25
print(AGE)
Redundant Keyword Arg
Rule ID: E1124
Description: A function call would result in assigning multiple values to a function parameter, one value from a positional argument and one from a keyword argument
def cube(x):
return x * x * x
cube(3, x=2) # [redundant-keyword-arg]
def cube(x):
return x * x * x
cube(3)
Assignment From No Return
Rule ID: E1111
Description: An assignment is done on a function call but the inferred function doesn't return anything
def subtract(x, y):
print(x - y)
result = subtract(7, 3) # [assignment-from-no-return]
def subtract(x, y):
return x - y
result = subtract(7, 3)
Assignment From None
Rule ID: E1128
Description: An assignment is done on a function call but the inferred function returns nothing but None
def compute():
return None
result = compute() # [assignment-from-none]
def compute():
return None
result = compute() if compute() else 42
Not Context Manager
Rule ID: E1129
Description: An instance in a with statement doesn't implement the context manager protocol (__enter__/__exit__)
class MyFileHandler:
def __enter__(self):
print('Opening file')
with MyFileHandler() as f: # [not-context-manager]
print('Processing file')
class MyFileHandler:
def __enter__(self):
print('Opening file')
def __exit__(self, *exc):
print('Closing file')
with MyFileHandler() as f:
print('Processing file')
Unhashable Dict Key
Rule ID: E1140
Description: Emitted when a dict key is not hashable (i.e. doesn't define hash method)
d = {[1, 2, 3]: 'numbers'} # [unhashable-dict-key]
d = {(1, 2, 3): 'tuple-key'}
Repeated Keyword
Rule ID: E1132
Description: Emitted when a function call got multiple values for a keyword
def greet(name, msg='Hello'):
return f'{msg}, {name}'
greet('John', msg='Hi', **{'msg': 'Welcome'}) # [repeated-keyword]
def greet(name, msg='Hello'):
return f'{msg}, {name}'
greet('John', msg='Hi')
Invalid Metaclass
Rule ID: E1139
Description: Emitted whenever we can detect that a class is using, as a metaclass, something which might be invalid for using as a metaclass
class Car(metaclass=str): # [invalid-metaclass]
pass
class Vehicle:
pass
class Car(Vehicle):
pass
Missing Kwoa
Rule ID: E1125
Description: A function call does not pass a mandatory keyword-only argument
def divide(dividend, *, divisor):
return dividend / divisor
def calculate(*args, **kwargs):
divide(*args) # [missing-kwoa]
def divide(dividend, *, divisor):
return dividend / divisor
def calculate(*args, **kwargs):
divide(*args, **kwargs)
No Value For Parameter
Rule ID: E1120
Description: A function call passes too few arguments
def multiply(x, y):
return x * y
multiply(4) # [no-value-for-parameter]
def multiply(x, y):
return x * y
multiply(4, 5)
Not An Iterable
Rule ID: E1133
Description: A non-iterable value is used in place where an iterable is expected
for char in 100: # [not-an-iterable]
pass
for char in '100':
pass
Not A Mapping
Rule ID: E1134
Description: A non-mapping value is used in place where mapping is expected
def print_animals(**animals):
print(animals)
print_animals(**list('cat', 'dog')) # [not-a-mapping]
def print_animals(**animals):
print(animals)
print_animals(**dict(cat=1, dog=2))
Invalid Sequence Index
Rule ID: E1126
Description: A sequence type is indexed with an invalid type. Valid types are ints, slices, and objects with an __index__ method
colors = ['red', 'green', 'blue']
print(colors['green']) # [invalid-sequence-index]
colors = ['red', 'green', 'blue']
print(colors[1])
Invalid Slice Index
Rule ID: E1127
Description: A slice index is not an integer, None, or an object with an __index__ method
NUMBERS = [1, 2, 3, 4]
FIRST_TWO = NUMBERS[:"2"] # [invalid-slice-index]
NUMBERS = [1, 2, 3, 4]
FIRST_TWO = NUMBERS[:2]
Too Many Function Args
Rule ID: E1121
Description: A function call passes too many positional arguments. Maximum number of arguments for function/method is 5 by default
class Vehicle:
def __init__(self, type):
self.type = type
car = Vehicle("sedan", "car", "2024") # [too-many-function-args]
class Vehicle:
def __init__(self, type, model):
self.type = type
self.model = model
car = Vehicle("sedan", "2024")
Unexpected Keyword Arg
Rule ID: E1123
Description: A function call passes a keyword argument that doesn't correspond to one of the function's parameter names
def greet(name="John", age=25):
print(f"Name: {name}, Age: {age}")
greet(name="Alice", age=30, gender="female") # [unexpected-keyword-arg]
def greet(name="John", age=25):
print(f"Name: {name}, Age: {age}")
greet(name="Alice", age=30)
Dict Iter Missing Items
Rule ID: E1141
Description: Emitted when trying to iterate through a dict without calling .items()
capitals = {"France": "Paris", "Japan": "Tokyo", "USA": "Washington D.C."}
for country, capital in capitals: # [dict-iter-missing-items]
print(f"{country} has capital {capital}.")
capitals = {"France": "Paris", "Japan": "Tokyo", "USA": "Washington D.C."}
for country, capital in capitals.items():
print(f"{country} has capital {capital}.")
Unsupported Membership Test
Rule ID: E1135
Description: Emitted when an instance in membership test expression doesn't implement membership protocol (__contains__/__iter__/__getitem__)
class Animal:
pass
dog = "dog" in Animal() # [unsupported-membership-test]
class Animal:
ANIMALS = ["dog", "cat"]
def __contains__(self, name):
return name in self.ANIMALS
dog = "dog" in Animal()
Unsubscriptable Object
Rule ID: E1136
Description: Emitted when a subscripted value doesn't support subscription (i.e. doesn't define __getitem__ method or __class_getitem__ for a class)
class Car:
pass
Car()[0] # [unsubscriptable-object]
class Car:
def __init__(self):
self.models = ["Sedan", "SUV", "Truck"]
def __getitem__(self, idx):
return self.models[idx]
Car()[0]
Keyword Arg Before Vararg
Rule ID: W1113
Description: When defining a keyword argument before variable positional arguments, one can end up in having multiple values passed for the aforementioned parameter in case the method is called with keyword arguments
def sum_values(x=None, *nums): # [keyword-arg-before-vararg]
return sum(nums) + (x if x else 0)
def sum_values(*nums, x=None):
return sum(nums) + (x if x else 0)
Non Str Assignment To Dunder Name
Rule ID: W1115
Description: Non-string value assigned to name Emitted when a non-string value is assigned to name
class Car:
pass
Car.__name__ = 100 # [non-str-assignment-to-dunder-name]
class Car:
pass
Car.__name__ = "Sedan"
Arguments Out Of Order
Rule ID: W1114
Description: Emitted when the caller's argument names fully match the parameter names in the function signature but do not have the same order
def process_args(first, second, third):
return first, second, third
def call_with_wrong_order():
first = 1
second = 2
third = 3
process_args( # [arguments-out-of-order]
first, third, second
)
def process_args(first, second, third):
return first, second, third
def call_with_correct_order():
first = 1
second = 2
third = 3
process_args(first, second, third)
Isinstance Second Argument Not Valid Type
Rule ID: W1116
Description: Second argument of isinstance is not a type Emitted when the second argument of an isinstance call is not a type
isinstance([1, 2, 3], dict) # [isinstance-second-argument-not-valid-type]
isinstance([1, 2, 3], list)
C Extension No Member
Rule ID: I1101
Description: A variable is accessed for non-existent member of C extension. Due to unavailability of source static analysis is impossible, but it may be performed by introspecting living objects in run-time
Unpacking Non Sequence
Rule ID: E0633
Description: Something which is not a sequence is used in an unpack assignment
a, b, c = 5 # [unpacking-non-sequence]
a, b, c = 1, 2, 3
Invalid All Object
Rule ID: E0604
Description: An invalid (non-string) object occurs in __all__
__all__ = (
None, # [invalid-all-object]
Car,
Bike,
)
class Car:
pass
class Bike:
pass
__all__ = ["Car", "Bike"]
class Car:
pass
class Bike:
pass
No Name In Module
Rule ID: E0611
Description: A name cannot be found in a module
from sys import sandwich # [no-name-in-module]
from sys import version
Undefined Variable
Rule ID: E0602
Description: An undefined variable is accessed
print(speed + 5) # [undefined-variable]
speed = 60
print(speed + 5)
Undefined All Variable
Rule ID: E0603
Description: An undefined variable name is referenced in all
__all__ = ["calculate_area"] # [undefined-all-variable]
def calc_area():
pass
__all__ = ["calc_area"]
def calc_area():
pass
Used Before Assignment
Rule ID: E0601
Description: A local variable is accessed before its assignment
print(greeting) # [used-before-assignment]
greeting = "Hi there!"
greeting = "Hi there!"
print(greeting)
Cell Var From Loop
Rule ID: W0640
Description: A variable used in a closure is defined in a loop. This will result in all closures using the same value for the closed-over variable
def parent_greeting(people):
messages = []
for person in people:
def say_hi():
# do something
print(f"Hi, {person}!") # [cell-var-from-loop]
if person.isalpha():
messages.append(say_hi)
for say_hi in messages:
# the "person" variable is evaluated when the function is called here,
# which is the last value it had in the loop - "Unknown"
say_hi()
parent_greeting(["John", "Paul", "George", "Ringo"])
# "Hi, Ringo!"
# "Hi, Ringo!"
# "Hi, Ringo!"
# "Hi, Ringo!"
Global Variable Undefined
Rule ID: W0601
Description: A variable is defined through the global statement but the variable is not defined in the module scope
def update_vegetable():
global VEGETABLE # [global-variable-undefined]
VEGETABLE = "potato"
VEGETABLE = "carrot"
def update_vegetable():
global VEGETABLE
VEGETABLE = "potato"
Self Cls Assignment
Rule ID: W0642
Description: Invalid assignment to self or cls in instance or class method respectively
class Vehicle:
@classmethod
def list_vehicles(cls):
cls = "car" # [self-cls-assignment]
def print_speed(self, *speeds):
self = "fast" # [self-cls-assignment]
speed = speeds[0]
print(speed)
class Vehicle:
@classmethod
def list_vehicles(cls):
vehicle = "car"
print(vehicle)
def print_speed(self, *speeds):
speed = speeds[0]
print(speed)
Unbalanced Tuple Unpacking
Rule ID: W0632
Description: There is an unbalanced tuple unpacking in assignment
colors = ("red", "green", "blue", "yellow")
red, green = colors # [unbalanced-tuple-unpacking]
colors = ("red", "green", "blue", "yellow")
red, green, *others = colors
Possibly Unused Variable
Rule ID: W0641
Description: A variable is defined but might not be used. The possibility comes from the fact that locals() might be used, which could consume or not the said variable
def choose_color(colors):
print(colors)
hue = "blue" # [possibly-unused-variable]
return locals()
def choose_color(colors):
current_locals = locals()
print(colors)
hue = "blue"
print(hue)
return current_locals
Redefined Builtin
Rule ID: W0622
Description: A variable or function overrides a built-in
def list(): # [redefined-builtin]
pass
def list_items():
pass
Redefine In Handler
Rule ID: W0623
Description: An exception handler assigns the exception to an existing name
try:
1/0
except ZeroDivisionError as e:
e = 'Error occurred' # [redefine-in-handler]
try:
1/0
except ZeroDivisionError as err:
err = 'Error occurred'
Redefined Outer Name
Rule ID: W0621
Description: A variable's name hides a name defined in the outer scope
counter = 20
def count_down(counter): # [redefined-outer-name]
for i in range(counter, 0, -1):
print(i)
counter = 20
def count_down(limit):
for i in range(limit, 0, -1):
print(i)
Unused Import
Rule ID: W0611
Description: An imported module or variable is not used
from os import getenv
from datetime import datetime # [unused-import]
API_KEY = getenv('API_KEY')
from os import getenv
API_KEY = getenv('API_KEY')
Unused Argument
Rule ID: W0613
Description: A function or method argument is not used
def area(length, width): # [unused-argument]
return length * length
def area(length, width):
return length * width
Unused Wildcard Import
Rule ID: W0614
Description: An imported module or variable is not used from a 'from X import *' style import
from collections import * # [unused-wildcard-import]
Counter(['apple', 'orange', 'banana'])
from collections import Counter
Counter(['apple', 'orange', 'banana'])
Unused Variable
Rule ID: W0612
Description: A variable is defined but not used
def greet():
first_name = "John"
last_name = "Doe" # [unused-variable]
print(f"Hello {first_name}")
def greet():
first_name = "John"
last_name = "Doe"
print(f"Hello {first_name} {last_name}")
Global Variable Not Assigned
Rule ID: W0602
Description: A variable is defined through the global statement but no assignment to this variable is done
FRUIT = "apple"
def update_fruit():
global FRUIT # [global-variable-not-assigned]
print(FRUIT)
FRUIT = "apple"
def update_fruit():
global FRUIT
FRUIT = "banana"
Undefined Loop Variable
Rule ID: W0631
Description: A loop variable (i.e. defined by a for loop or a list comprehension or a generator expression) is used outside the loop
def find_odd_number(numbers):
for x in numbers:
if x % 2 != 0:
break
return x # [undefined-loop-variable]
def find_odd_number(numbers):
for x in numbers:
if x % 2 != 0:
return x
return None
Global Statement
Rule ID: W0603
Description: You use the global statement to update a global variable. Pylint discourages this usage, but it doesn't mean you cannot use it.
num = 5
def update_num():
global num # [global-statement]
num = 15
print(num)
update_num()
print(num)
num = 5
def update_num():
print(num)
return 15
num = update_num()
print(num)
Global At Module Level
Rule ID: W0604
Description: You use the global statement at the module level since it has no effect
count = 10
global count # [global-at-module-level]
count = 10
Security
Cyclopt scans your Python code for security vulnerabilities, injection, insecure configuration, weak cryptography, data exposure, and more. Each finding below lists its Rule ID, what it detects and how to fix it, a severity, and the relevant CWE/OWASP classification.
Formatted String Bashoperator
Rule ID: formatted-string-bashoperator
Description: Building the bash_command for BashOperator via f-strings, % formatting, .format(), or + concatenation splices raw values straight into the shell command, so any variable derived from external input can inject arbitrary Bash. Pass parameters through Airflow Jinja templating (e.g. {{ params.x }} rendered by the BashOperator template engine) and quote them with shlex.quote instead of formatting the string yourself.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Attr Mutable Initializer
Rule ID: attr-mutable-initializer
Description: Assigning a literal {}, [], list(...), set(...), or dict(...) as a class attribute under @attr.s(auto_attribs=True) or @attrs.define evaluates the container exactly once at class creation, so every instance ends up sharing and mutating the same object. Replace the literal with attr.ib(factory=list) / attrs.field(factory=dict) (or the appropriate factory) so each instance receives a fresh container.
Dangerous Asyncio Create Exec
Rule ID: dangerous-asyncio-create-exec
Description: Arguments to asyncio.create_subprocess_exec (or asyncio.subprocess.create_subprocess_exec) are derived from the Lambda event payload, which is attacker-controlled at the API Gateway boundary. Even when the program path is fixed, an interpreted shell wrapper like sh -c $CMD re-introduces command injection (CWE-78). Build the argv list from validated literals and escape any dynamic token with shlex.quote.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Exec
Rule ID: dangerous-asyncio-exec
Description: An event-loop transport call loop.subprocess_exec(...) is receiving an argument tainted by the Lambda event payload. Because the asyncio low-level transport spawns a child process directly, attacker-controlled tokens become command arguments and, when combined with a shell wrapper, allow OS command injection (CWE-78). Use a hard-coded executable path and pass user-supplied values only as separate, shlex.quote-wrapped argv elements.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Shell
Rule ID: dangerous-asyncio-shell
Description: A shell-interpreted asyncio call (asyncio.create_subprocess_shell, asyncio.subprocess.create_subprocess_shell, or loop.subprocess_shell) takes its command string from the Lambda event payload. Because the entire string is forwarded to /bin/sh -c, metacharacters such as ;, |, or backticks supplied by the caller execute as additional commands (CWE-78). Switch to the argv-form create_subprocess_exec API or rebuild the command from validated constants.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Spawn Process
Rule ID: dangerous-spawn-process
Description: Process-spawning helpers from the os module (os.spawnl, os.spawnv, os.posix_spawn, os.startfile and friends) are invoked with an argument that flows from the Lambda event. These primitives execute the supplied program directly, and when used with an sh -c wrapper they expand attacker input into shell tokens, enabling command injection (CWE-78). Migrate to subprocess.run with shell=False and a list of arguments built from validated input.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Subprocess Use
Rule ID: dangerous-subprocess-use
Description: A subprocess.* call is invoked with shell=True while the command string carries data from the Lambda event payload. With shell=True the command is parsed by /bin/sh, so any shell metacharacters supplied by the caller execute arbitrary commands (CWE-78). Drop shell=True and pass the program plus arguments as a list, using shlex.split only on trusted templates and shlex.quote to wrap dynamic values.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous System Call
Rule ID: dangerous-system-call
Description: The Lambda event payload reaches a shell-backed os primitive — os.system, os.popen, or one of its popen2/3/4 variants. Each of these forwards the full command string to /bin/sh, so attacker-supplied shell metacharacters execute as additional commands (CWE-78). Replace with subprocess.run([...], shell=False) and pass dynamic values as discrete argv elements.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dynamodb Filter Injection
Rule ID: dynamodb-filter-injection
Description: A ScanFilter or QueryFilter passed to a boto3 DynamoDB Table.scan/Table.query call is constructed from the Lambda event payload. Because the filter conditions reach the DynamoDB service unchanged, an attacker can manipulate operators and attribute names to bypass intended access controls (CWE-943). Build the filter mapping explicitly from validated fields, or use boto3.dynamodb.conditions.Attr/Key expressions with parameterised values.
CWE: CWE-943 · OWASP: A01:2017
Mysql Sqli
Rule ID: mysql-sqli
Description: A query string passed to MySQLCursor.execute / executemany from the MySQL Connector/Python (mysql.connector) is built from the Lambda event payload. Concatenating untrusted input into SQL exposes the function to injection (CWE-89), letting attackers exfiltrate or modify rows. Use the connector's %s placeholders and pass the values as the second argument, e.g. cursor.execute("SELECT * FROM projects WHERE status = %s", (status,)).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Psycopg Sqli
Rule ID: psycopg-sqli
Description: SQL handed to a psycopg2 cursor via execute, executemany, or mogrify is composed from the Lambda event payload. psycopg2 only escapes parameters when they are supplied as the second argument; merging them into the query string itself leaves the call open to SQL injection (CWE-89). Pass values as a tuple — for example cursor.execute("SELECT * FROM projects WHERE status = %s", (status,)) — and rely on psycopg2.sql.SQL for any dynamic identifiers.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Pymssql Sqli
Rule ID: pymssql-sqli
Description: A pymssql cursor receives a query string assembled from the Lambda event payload through cursor.execute(...). The pymssql driver only parameterises values supplied through its %s/%(name)s placeholder syntax — string concatenation reaches SQL Server verbatim and permits SQL injection (CWE-89). Refactor the call to cursor.execute("SELECT * FROM projects WHERE status = %s", (status,)).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Pymysql Sqli
Rule ID: pymysql-sqli
Description: PyMySQL's cursor execute(...) method is invoked with a query string that flows from the Lambda event payload. PyMySQL forwards the literal SQL to MySQL whenever the values are not bound through %s placeholders, opening the door to SQL injection (CWE-89). Bind parameters instead, e.g. cursor.execute("SELECT * FROM projects WHERE status = %s", (status,)), and quote any dynamic identifier via pymysql.converters.escape_string or an allow-list.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Sqlalchemy Sqli
Rule ID: sqlalchemy-sqli
Description: A raw SQL string assembled from the Lambda event payload is being executed via SQLAlchemy's Connection.execute / Session.execute. When the SQL is not wrapped in sqlalchemy.text(...) with bound parameters, SQLAlchemy passes the string straight to the DBAPI, and attacker input can alter the query (CWE-89). Use the ORM Query API or connection.execute(text("SELECT ... WHERE status = :status"), {"status": status}).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Tainted Code Exec
Rule ID: tainted-code-exec
Description: Python's built-in eval(...) / exec(...) interprets its argument as code, and here the expression is sourced from the Lambda event payload. That gives an external caller the ability to run arbitrary Python in the Lambda runtime (CWE-95), reading secrets, mutating state, or pivoting to other AWS APIs. Replace dynamic evaluation with ast.literal_eval, a parser, or an explicit dispatch table of allowed operations.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Tainted HTML Response
Rule ID: tainted-html-response
Description: The Lambda response body returned to API Gateway is set to a value derived from the event payload while the response advertises Content-Type: text/html. Browsers will render that body as markup, so unescaped attacker input becomes executable script (CWE-79, reflected XSS). Encode the value with html.escape(...) or, preferably, render through a templating engine (e.g. Jinja2 with autoescape) before placing it in the response.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Tainted HTML String
Rule ID: tainted-html-string
Description: HTML markup is being assembled by hand — through % formatting, str.format, + concatenation, or an f-string — with a fragment carrying data from the Lambda event. This bypasses any auto-escaping a templating engine would apply, so the resulting page lets attackers inject <script> tags (CWE-79, stored or reflected XSS). Render with a real template (e.g. Jinja2 Environment(autoescape=True)) or wrap the dynamic substring with markupsafe.escape(...).
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Tainted Pickle Deserialization
Rule ID: tainted-pickle-deserialization
Description: A deserialization sink (pickle.load(s), cPickle.load(s), _pickle.load(s), dill.load(s), or shelve.open) is fed data that ultimately originates in the Lambda event payload. The pickle protocol invokes __reduce__ constructors during unpickling, so a crafted byte stream executes arbitrary Python code in the Lambda process (CWE-502). Switch to a structured, code-free format such as JSON via json.loads, or sign the payload (e.g. HMAC) before unpickling.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Tainted SQL String
Rule ID: tainted-sql-string
Description: A SELECT/INSERT/UPDATE/DELETE-shaped literal is being concatenated with data from the Lambda event using +, %, str.format, or an f-string. Whatever DBAPI driver later receives the resulting string will execute it verbatim, allowing attacker-controlled fragments to alter the query (CWE-89). Compose queries with the driver's placeholder syntax (%s / ?) and pass user-supplied values as a separate parameter sequence — never by string interpolation.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Bokeh Deprecated Apis
Rule ID: bokeh-deprecated-apis
Description: Importing bokeh.layouts.widgetbox or bokeh.models.graphs.from_networkx references APIs that were retired in newer Bokeh releases and will raise ImportError on upgrade. Switch to bokeh.layouts.column/row for widgetbox, and bokeh.plotting.from_networkx for the graph helper, so the code stays compatible with current Bokeh versions.
Hardcoded Token
Rule ID: hardcoded-token
Description: A literal string is being passed as aws_access_key_id, aws_secret_access_key, or aws_session_token when constructing a boto3.client(...) / boto3.Session(...) (or a chained resource call). Embedding AWS credentials in source code exposes them to anyone who reads the repository, build artifact, or container image (CWE-798), and once leaked they cannot be rotated quickly. Load credentials from the default provider chain (IAM role, environment, or ~/.aws/credentials) or fetch them from Secrets Manager / Parameter Store at runtime.
CWE: CWE-798 · OWASP: A07:2021, A07:2025
Check Is None Explicitly
Rule ID: check-is-none-explicitly
Description: Guarding $X == 0 with the truthiness check $X and ... makes the whole expression unreachable because 0 is falsy in Python, so the short-circuit on the left always rejects the only value that would have satisfied the right. If the intent was to skip None while still allowing 0, compare explicitly with $X is not None and $X == 0.
Socket Shutdown Close
Rule ID: socket-shutdown-close
Description: Calling $SOCK.shutdown(...) followed by $SOCK.close() without a try/finally leaks the file descriptor whenever shutdown raises OSError (a common case when the peer has already closed the connection), because the subsequent close is skipped. Wrap the shutdown call in try: ... finally: $SOCK.close() so the socket is always released.
Suppressed Exception Handling Finally Break
Rule ID: suppressed-exception-handling-finally-break
Description: Putting a break, continue, or return inside a finally block silently discards any exception that is propagating out of the try/except and overrides any pending return value, so real errors disappear and control flow becomes very hard to reason about. Move the early exit into the try or else branch and keep finally for cleanup only.
Crypto Mode Without Authentication
Rule ID: crypto-mode-without-authentication
Description: Building a Cipher(...) with modes.CBC, modes.CTR, modes.CFB, or modes.OFB from cryptography.hazmat.primitives.ciphers without pairing the ciphertext with an HMAC leaves no integrity protection, exposing the construction to padding-oracle and bit-flipping attacks. Prefer an AEAD construction such as modes.GCM so encryption and authentication are produced together.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Empty Aes Key
Rule ID: empty-aes-key
Description: AES.new("", ...) is being constructed with an empty key string. AES is only secure when keyed with 16, 24, or 32 bytes of unpredictable material; an empty key effectively disables encryption and lets anyone decrypt or forge ciphertexts (CWE-327). Load a real key from a secrets store (for instance via os.urandom(32) at provisioning time stored in Secrets Manager) and reject empty values before calling AES.new.
CWE: CWE-327, CWE-310 · OWASP: A6:2017
Insecure Cipher Algorithm Arc4
Rule ID: insecure-cipher-algorithm-arc4
Description: cryptography.hazmat.primitives.ciphers.algorithms.ARC4 instantiates the broken RC4 stream cipher. RC4's keystream is biased in its first bytes and has been used to break TLS and WEP (CWE-327); the cryptography library exposes it only for legacy interop. Replace it with cryptography.fernet.Fernet for application-level encryption, or with AES-GCM via cryptography.hazmat.primitives.ciphers.aead.AESGCM when working at the primitive level.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Blowfish
Rule ID: insecure-cipher-algorithm-blowfish
Description: Selecting cryptography.hazmat.primitives.ciphers.algorithms.Blowfish configures a 64-bit block cipher that is vulnerable to birthday/Sweet32 attacks after roughly 32 GiB of ciphertext under one key (CWE-327). Even Bruce Schneier, its designer, recommends moving away from it. Use cryptography.hazmat.primitives.ciphers.algorithms.AES (128/256-bit) together with an AEAD mode such as modes.GCM, or the higher-level cryptography.fernet.Fernet wrapper.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Idea
Rule ID: insecure-cipher-algorithm-idea
Description: Building a cipher around cryptography.hazmat.primitives.ciphers.algorithms.IDEA keeps the legacy 64-bit IDEA primitive in use. The small block size makes it susceptible to Sweet32-style collision attacks, and weak-key classes further reduce its effective strength (CWE-327). Pick algorithms.AES with modes.GCM (or use cryptography.hazmat.primitives.ciphers.aead.AESGCM) for new code; reserve IDEA only for decrypting historical data that cannot be re-encrypted.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Mode ECB
Rule ID: insecure-cipher-mode-ecb
Description: Wrapping a cipher with cryptography.hazmat.primitives.ciphers.modes.ECB(...) produces deterministic, block-aligned ciphertext: identical plaintext blocks encrypt to identical ciphertext blocks, leaking structure (CWE-327) — the classic "ECB penguin" failure. Use an AEAD mode such as modes.GCM(iv) (or aead.AESGCM) and supply a fresh nonce per message so confidentiality and integrity are both protected.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm MD5
Rule ID: insecure-hash-algorithm-md5
Description: Calling cryptography.hazmat.primitives.hashes.MD5() selects the MD5 digest, for which practical collision attacks have existed since 2004. Using it for fingerprints, HMAC keys, or digital signatures lets an attacker craft different inputs with the same hash (CWE-327). Switch to hashes.SHA256() (or hashes.SHA3_256() / hashes.BLAKE2b(64)); reserve MD5 only for non-security checksums such as ETag matching.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm SHA1
Rule ID: insecure-hash-algorithm-sha1
Description: cryptography.hazmat.primitives.hashes.SHA1 is in use here. SHA-1 has practical collision attacks and must not be used for digital signatures, certificate fingerprints, or any integrity-critical primitive. Replace with hashes.SHA256() or hashes.SHA3_256() from the same module.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insufficient Dsa Key Size
Rule ID: insufficient-dsa-key-size
Description: The call to cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key is using a key_size below 2048 bits, which falls short of the NIST SP 800-57 minimum strength. Raise the parameter to 2048 (or higher) so that the resulting DSA key is computationally resistant to factoring attacks.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Insufficient Ec Key Size
Rule ID: insufficient-ec-key-size
Description: Generating an EC private key with ec.SECP192R1, ec.SECT163K1, or ec.SECT163R2 via cryptography.hazmat.primitives.asymmetric.ec.generate_private_key produces a curve below the 224-bit floor recommended by NIST SP 800-57. Switch to a stronger curve such as ec.SECP256R1 or ec.SECP384R1 to retain meaningful security margin.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Insufficient Rsa Key Size
Rule ID: insufficient-rsa-key-size
Description: RSA keys produced through cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key with a key_size under 2048 bits are below the NIST minimum and considered factorable given modern compute. Pass key_size=2048 or larger so the generated key meets current cryptographic strength guidance.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Require Encryption
Rule ID: require-encryption
Description: The Dask distributed.security.Security(...) context is being constructed with require_encryption=False, which lets the scheduler accept unencrypted worker connections and silently transmit task payloads in cleartext. Pass require_encryption=True so the cluster refuses non-TLS endpoints.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Access Foreign Keys
Rule ID: access-foreign-keys
Description: Reading $X.user.id triggers an additional SELECT on the related User row just to fetch the primary key the ORM already stores locally. Use the foreign-key column directly with $X.user_id to read the value from the existing instance without round-tripping to the database.
Avoid Insecure Deserialization
Rule ID: avoid-insecure-deserialization
Description: Deserialising request-controlled bytes through pickle.load(s), _pickle, cPickle, shelve, dill, or yaml.load (without SafeLoader) reconstructs arbitrary Python objects and can execute attacker-supplied gadgets, yielding remote code execution inside the Django process. Parse untrusted input with json instead, or use yaml.safe_load and explicit Dumper=yaml.SafeDumper / Loader=yaml.SafeLoader arguments.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Mark Safe
Rule ID: avoid-mark-safe
Description: Wrapping a dynamic value in django.utils.safestring.mark_safe(...) flags the resulting string as already-escaped, so the template engine renders it verbatim and any attacker-influenced characters become live HTML — a classic XSS sink. Build markup with django.utils.html.format_html(...), which auto-escapes interpolated arguments while keeping the surrounding template static.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Avoid Query Set Extra
Rule ID: avoid-query-set-extra
Description: Calling QuerySet.extra(...) lets the caller splice raw SQL fragments into where, tables, select, and order_by, none of which Django escapes — the Django team itself recommends avoiding this API for exactly that reason. Rewrite the query with ORM methods like filter(...), annotate(...), or Func expressions; if .extra is unavoidable, keep dynamic values inside the params=[...] argument with %s placeholders.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Avoid Raw SQL
Rule ID: avoid-raw-sql
Description: Building a query with Model.objects.raw(...) or django.db.models.expressions.RawSQL(...) from a non-literal string skips the ORM's parameter binding, so any concatenated user value is injected straight into the executed SQL. Express the query through the ORM (filter, annotate, Q, F) or pass the dynamic data via the params=[...] argument with %s placeholders instead of embedding it in the SQL text.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Class Extends Safestring
Rule ID: class-extends-safestring
Description: Subclassing django.utils.safestring.SafeString, SafeText, or SafeData produces a type that the template autoescaper unconditionally treats as already-safe HTML, so every instance — including ones built from user data — is rendered without escaping. Drop the subclass and, where literal HTML is genuinely required, call mark_safe(...) on a known-static string built with format_html(...).
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Command Injection OS System
Rule ID: command-injection-os-system
Description: Untrusted data from a Django 'request' reaches 'os.system', which runs the string through '/bin/sh' and lets shell metacharacters (';', '|', '' ' '', '$()') start additional commands. The result is OS command injection executed as the application user. Replace the call with 'subprocess.run([...], shell=False)' and pass arguments as a list of separate tokens drawn from a validated allow-list.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Context Autoescape Off
Rule ID: context-autoescape-off
Description: Constructing a django.template.Context (or a backend-options dict) with "autoescape": False turns off HTML escaping for every variable rendered through that context, so any user-controlled value flowing into {{ ... }} is interpreted as live HTML. Remove the autoescape: False entry — or set it back to True — and use the |safe filter or mark_safe(...) only for the specific values that genuinely need it.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Csv Writer Injection
Rule ID: csv-writer-injection
Description: Request data flows into csv.writer.writerow / writerows / writeheader without neutralising leading =, +, -, @ characters. When the exported CSV is opened in Excel, LibreOffice or Google Sheets, those values are interpreted as formulas and can exfiltrate data or run attacker-controlled code on the recipient's machine (CSV / formula injection). Use defusedcsv.writer as a drop-in replacement or prefix untrusted cells with a single quote before writing.
CWE: CWE-1236 · OWASP: A01:2017, A03:2021, A05:2025
Custom Expression As SQL
Rule ID: custom-expression-as-sql
Description: Implementing or calling as_sql(...) on a Django ORM expression bypasses the parameterised compiler and lets the author splice raw fragments straight into the generated query. If any user-controlled value reaches $EXPRESSION.as_sql, the application becomes vulnerable to SQL injection — review the implementation and return SQL placeholders plus a params tuple rather than concatenating strings.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Debug Template Tag
Rule ID: debug-template-tag
Description: The {% debug %} template tag prints the current context dictionary and the list of loaded modules whenever DEBUG = True, leaking variable names, settings, and stack-related metadata directly into the rendered page. Delete the tag from the template — debugging context belongs in the Django Debug Toolbar or in logs, never in user-facing markup.
CWE: CWE-489 · OWASP: A06:2017
Direct Use Of Httpresponse
Rule ID: direct-use-of-httpresponse
Description: Returning dynamic content through django.http.HttpResponse(...) (or its HttpResponseBadRequest, HttpResponseNotFound, etc. variants) with a text/html body sends the payload to the browser without passing through the template autoescaper, so any embedded user data is rendered as raw HTML. Render through django.shortcuts.render(request, "template.html", {...}) so Django escapes interpolated variables automatically.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Django DB Model Save Super
Rule ID: django-db-model-save-super
Description: Overriding save() on the Django model $MODEL without invoking super().save(...) skips the inherited persistence logic, so the row is never written and field defaults, signals, and auto-now timestamps are bypassed. Call super().save(*args, **kwargs) (or super($MODEL, self).save(...)) inside the override so the ORM still commits the instance.
Django No CSRF Token
Rule ID: django-no-csrf-token
Description: This state-changing HTML <form> (POST/PUT/PATCH/DELETE) omits the {% csrf_token %} tag, leaving the endpoint open to cross-site request forgery. Without the token Django's CsrfViewMiddleware will reject legitimate submissions and any view bypassing the middleware accepts requests forged from another origin. Insert {% csrf_token %} inside the form body.
CWE: CWE-352
Django Secure Set Cookie
Rule ID: django-secure-set-cookie
Description: Calling response.set_cookie(...) without secure=True, httponly=True, and samesite="Lax" (or "Strict") leaves the cookie reachable from JavaScript, transmittable over plain HTTP, and attachable to cross-site requests — common preconditions for session hijacking and CSRF. Pass the three flags explicitly, and only relax one when the cookie demonstrably requires that capability.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
Django Using Request Post After Is Valid
Rule ID: django-using-request-post-after-is-valid
Description: After $FORM.is_valid() succeeds, reading from request.POST skips the type coercion and validation that the form just performed and re-exposes raw, untrusted strings. Switch to $FORM.cleaned_data["field"], which returns the sanitized, correctly-typed values produced by each field's clean() method.
CWE: CWE-20
Extends Custom Expression
Rule ID: extends-custom-expression
Description: Subclassing internal Django ORM expression types such as Func, Expression, RawSQL, Subquery, or Case (in $CLASS) requires overriding the SQL compilation phase, which is an unparameterised seam in the ORM. If user input reaches the overridden as_sql, template, or arg_joiner logic, the resulting query becomes injectable — keep dynamic values in the params list and never f-string them into the SQL template.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Filter With Is Safe
Rule ID: filter-with-is-safe
Description: Registering a custom template filter with @register.filter(is_safe=True) promises Django that the filter neither introduces nor removes HTML-significant characters, so the engine emits its return value without escaping. If the filter appends, modifies, or formats any of its arguments — particularly anything originating from request data — that promise is broken and a stored XSS is introduced. Drop is_safe=True and call mark_safe(format_html(...)) on a known-safe literal only when truly necessary.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Formathtml Fstring Parameter
Rule ID: formathtml-fstring-parameter
Description: Pre-interpolating values into the first argument of django.utils.html.format_html(...) — via f-strings, % formatting, or str.format — bypasses the per-argument escaping that format_html is designed to perform, because the interpolated characters arrive already baked into the "static" template. Keep the first argument a literal string with {} placeholders and pass user-controlled values as the remaining positional arguments so they are escaped before substitution.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Global Autoescape Off
Rule ID: global-autoescape-off
Description: Setting "autoescape": False inside the OPTIONS of the TEMPLATES backend in settings.py disables HTML escaping for every template the project renders, so a single unescaped variable anywhere in the codebase becomes a site-wide XSS. Restore "autoescape": True (the Django default) and opt out per-block with {% autoescape off %} or per-value with mark_safe(...) only where the content is provably static.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Globals As Template Context
Rule ID: globals-as-template-context
Description: Passing globals() to django.shortcuts.render or Template.render hands every module-level name (including imported callables and settings) to the template engine. A crafted template path or attribute lookup can then invoke arbitrary functions, opening a server-side template injection (SSTI) hole. Build an explicit context dictionary such as {"user": user, "items": items} and pass that instead.
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Globals Misuse Code Execution
Rule ID: globals-misuse-code-execution
Description: A value pulled from request.GET / request.POST indexes globals() and the resulting object is then invoked, letting a client choose which module-level callable the view executes. This is a direct remote code execution path. Replace the dynamic lookup with an explicit allow-list such as dispatch = {"create": create_item, "delete": delete_item} and only call dispatch[name] after validating the key.
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Hashids With Django Secret
Rule ID: hashids-with-django-secret
Description: Seeding hashids.Hashids with django.conf.settings.SECRET_KEY reuses the framework's master secret as a reversible obfuscation salt. The Hashids algorithm is not cryptographically secure: given enough emitted IDs an attacker can recover the salt and therefore your SECRET_KEY, which is used to sign sessions, password resets and CSRF tokens. Derive a separate, dedicated salt for Hashids and rotate SECRET_KEY if it was leaked this way.
CWE: CWE-327 · OWASP: A02:2021
HTML Magic Method
Rule ID: html-magic-method
Description: Implementing a __html__ magic method on a class signals to conditional_escape and the Django template engine that the object already produces escape-safe HTML, so its rendered output skips autoescaping. If the returned string is built from user input, attacker payloads land in the page unescaped — favour explicit mark_safe(format_html(...)) calls at the rendering site so the safe boundary is visible in code review.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
HTML Safe
Rule ID: html-safe
Description: Applying @django.utils.html.html_safe to a class auto-injects an __html__ method that returns str(self), which causes Django's template engine to render every instance without HTML escaping. Any field or attribute that contributes to __str__ becomes a potential XSS sink — prefer constructing a single mark_safe(format_html(...)) value at render time rather than marking an entire class as HTML-safe.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Locals As Template Context
Rule ID: locals-as-template-context
Description: locals() is forwarded as the rendering context for django.shortcuts.render or Template.render, leaking every name that happens to be in scope, including form objects, database rows, and transient secrets. Beyond data exposure, a template that references a callable from that scope can be coaxed into server-side template injection. Build an explicit context dict containing only the values the template needs (e.g. {"profile": profile}).
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Mass Assignment
Rule ID: mass-assignment
Description: Expanding request.POST / request.GET with ** into Model.objects.create or an instance update() lets a caller set any model attribute, including sensitive ones such as is_staff, is_superuser, or foreign keys to other users' records. This is a textbook mass-assignment vulnerability. Bind each field explicitly (Model(name=form.cleaned_data["name"], ...)) or use a ModelForm / DRF Serializer with a restrictive fields list.
CWE: CWE-915 · OWASP: A08:2021, A08:2025
Missing Throttle Config
Rule ID: missing-throttle-config
Description: The REST_FRAMEWORK settings dictionary defines no DEFAULT_THROTTLE_CLASSES / DEFAULT_THROTTLE_RATES, so every DRF endpoint accepts unlimited requests per client. Without throttling, attackers can drive expensive views into resource exhaustion or brute-force authentication. Register at least one throttle class (e.g. AnonRateThrottle, UserRateThrottle) and matching rates such as {"anon": "100/hour", "user": "1000/hour"}.
CWE: CWE-770 · OWASP: A05:2021, A06:2017, A02:2025
Nan Injection
Rule ID: nan-injection
Description: Request-supplied strings are cast directly through float(...), bool(...), or complex(...). Submitting the literal "nan" (or any capitalisation, plus inf/-inf) yields IEEE-754 special values that silently break comparisons - nan > x and nan < x are both False, bool("nan") is True - and can flip authorisation or pricing checks. Validate the string against a numeric regex first, or use int(...) with explicit range checks where you expect integers.
CWE: CWE-704
No CSRF Exempt
Rule ID: no-csrf-exempt
Description: Decorating a view with @django.views.decorators.csrf.csrf_exempt removes Django's CSRF middleware protection from that endpoint, allowing a malicious site to submit authenticated state-changing requests on behalf of the logged-in user. Remove the decorator and keep the standard CsrfViewMiddleware, or — if the endpoint really must accept cross-origin calls — protect it with a signed token or proper CORS plus origin validation.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
No Null String Field
Rule ID: no-null-string-field
Description: Setting null=True on a CharField or TextField creates two distinct "empty" values in the database — NULL and "" — which complicates queries and breaks Django's own convention of storing missing strings as empty. Drop null=True from string fields and rely on the empty string as the single "no data" representation.
Nontext Field Must Set Null True
Rule ID: nontext-field-must-set-null-true
Description: Declaring blank=True on a non-text Django model field without null=True lets forms accept an empty value but leaves the database column NOT NULL, so saving blank data raises IntegrityError. For numeric, boolean, date, and foreign-key fields the Django convention is to allow NULL at the DB layer — add null=True alongside blank=True.
Open Redirect
Rule ID: open-redirect
Description: The URL passed to django.shortcuts.redirect (or HttpResponseRedirect) originates from the request without first being validated by django.utils.http.url_has_allowed_host_and_scheme (or the legacy is_safe_url). An attacker can craft a link such as ?next=https://evil.example.com to bounce the victim off your domain for phishing. Validate the candidate URL against an allow-list of hosts before redirecting, or redirect to a named route.
CWE: CWE-601 · OWASP: A01:2021, A01:2025
Password Empty String
Rule ID: password-empty-string
Description: $MODEL.set_password($VAR) is being called with the empty string and the user is then saved, producing a real, hashed password that any caller can log in with by submitting "". To mark the account as unable to authenticate via password, call $MODEL.set_unusable_password() (or pass None to set_password) before save() so the stored hash is rejected by Django's authentication backend.
CWE: CWE-521 · OWASP: A07:2021, A07:2025
Path Traversal File Name
Rule ID: path-traversal-file-name
Description: A request value is interpolated into a filename template ending in .log, .zip, .txt, .csv, .xml, or .html via % or str.format, and the resulting string is later passed to a filesystem sink. Path components such as ../../etc/passwd will escape the intended directory and disclose or overwrite arbitrary files. Constrain the input to a basename, anchor it under a known root, and verify with os.path.realpath(path).startswith(root).
CWE: CWE-22 · OWASP: A05:2017, A01:2021, A01:2025
Path Traversal Join
Rule ID: path-traversal-join
Description: Request-derived input is concatenated by os.path.join(...) and the resulting path is opened with open(...). os.path.join does not sanitize traversal sequences and silently discards earlier components when given an absolute path, so a value like ../../secrets.env reaches the filesystem unchanged. Canonicalize the joined path with os.path.realpath(...) and reject anything outside the allowed root, or use pathlib.Path.resolve() plus an is_relative_to(...) check.
CWE: CWE-22 · OWASP: A05:2017, A01:2021, A01:2025
Path Traversal Open
Rule ID: path-traversal-open
Description: The path argument of the built-in open(...) (including the with open(...) context-manager form) comes straight from a Django request, either directly or via %, str.format, f-string, or concatenation. A caller supplying ../../etc/passwd reads arbitrary files; in write mode they can overwrite source or configuration files. Normalize with os.path.realpath(...) (or pathlib.Path.resolve()) and ensure the final path stays within a fixed allow-listed directory.
CWE: CWE-22 · OWASP: A05:2017, A01:2021, A01:2025
Raw HTML Format
Rule ID: raw-html-format
Description: A literal HTML string containing an opening tag (<div ..., <a ..., etc.) is composed with %, str.format, +, or an f-string from values pulled off the request object, bypassing Django's template autoescaping. Any unescaped attribute or text segment becomes a cross-site scripting vector. Render through django.shortcuts.render(...) with a proper template, or wrap each fragment in django.utils.html.escape(...) (or use format_html(...)) before concatenation.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Reflected Data Httpresponse
Rule ID: reflected-data-httpresponse
Description: Request data is echoed straight into the body of django.http.HttpResponse, so the browser interprets whatever the caller submits as HTML. This classic reflected XSS lets an attacker run JavaScript in the victim's session and steal cookies, CSRF tokens, or DOM contents. Render the response through a template (render(request, ...)) so autoescaping kicks in, or escape the value first with django.utils.html.escape(...).
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Reflected Data Httpresponsebadrequest
Rule ID: reflected-data-httpresponsebadrequest
Description: The 400 error body returned by django.http.HttpResponseBadRequest embeds untrusted request data without escaping, so an attacker can craft a URL or payload that triggers the error path and reflects HTML/JavaScript back to the victim. Even error responses are rendered by the browser, so this is still reflected XSS. Escape the value with django.utils.html.escape(...) or return a templated error page instead.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Request Data Fileresponse
Rule ID: request-data-fileresponse
Description: django.http.FileResponse receives a file handle (or path) derived from a request parameter, letting a client choose which file the server streams back. Traversal payloads such as ../../etc/passwd will reach open(...) unchanged and exfiltrate arbitrary files. Look the requested item up by primary key in a FileField model and call default_storage.open(...) on the stored relative path, or validate with os.path.realpath(...) against a fixed root directory.
CWE: CWE-22 · OWASP: A05:2017, A01:2021, A01:2025
Request Data Write
Rule ID: request-data-write
Description: Untrusted request data is streamed into a file-like object's .write(...) method, allowing an attacker to inject CRLF sequences (forging extra log entries), corrupt structured files, or exhaust disk space and trigger a denial-of-service. Strip newlines and control characters, cap the byte length you accept, and write through a structured serializer (e.g. json.dumps) rather than concatenating untrusted strings.
CWE: CWE-93 · OWASP: A03:2021, A05:2025
SQL Injection DB Cursor Execute
Rule ID: sql-injection-db-cursor-execute
Description: The SQL string handed to cursor.execute(...) is built from request data using %, str.format, f-string, or +, instead of bound parameters. Whatever the client sends becomes part of the query, enabling classic SQL injection against the connection opened by django.db.connection.cursor(). Always supply the query as a literal with %s placeholders and pass a tuple/list of values as the second argument: cursor.execute("SELECT ... WHERE id = %s", [user_id]).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
SQL Injection Using Extra Where
Rule ID: sql-injection-using-extra-where
Description: A request value is interpolated into the where=[...] argument of $MODEL.objects.extra(...), so the attacker controls a fragment of raw SQL that Django pastes verbatim into the generated query. This bypasses every protection of the ORM and yields SQL injection. Replace extra(where=[...]) with proper ORM lookups such as $MODEL.objects.filter(field=value), or, if extra is unavoidable, use %s placeholders and pass values via the params= argument.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
SQL Injection Using Raw
Rule ID: sql-injection-using-raw
Description: $MODEL.objects.raw(...) is called with a SQL string that already contains request-derived data, so the manager has no chance to parameterise it before running the query on the database backend. This is a SQL injection sink. Move filter logic to a regular QuerySet such as $MODEL.objects.filter(field=value), or supply placeholders and pass user values via the params=[...] argument of raw().
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
SQL Injection Using Rawsql
Rule ID: sql-injection-using-rawsql
Description: Untrusted request data is concatenated into the SQL string handed to django.db.models.expressions.RawSQL(...), leaving the column expression fully attacker-controlled. The injected fragment will be executed inside every annotated query that uses this expression. Pass values through the second positional params argument as a list of bound parameters and keep only %s placeholders in the SQL literal.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
SSRF Injection Requests
Rule ID: ssrf-injection-requests
Description: The URL passed to requests.get / requests.post (or any other requests.$METHOD(...) call) is assembled from data on the current Django request. A caller can therefore force the server to fetch internal addresses such as http://169.254.169.254/ (cloud metadata), http://localhost:5984/, or other intranet hosts, exfiltrating credentials and bypassing firewall boundaries (SSRF). Parse the URL, validate the scheme and host against a fixed allow-list, and refuse private IP ranges before issuing the outbound request.
CWE: CWE-918 · OWASP: A10:2021, A01:2025
SSRF Injection Urllib
Rule ID: ssrf-injection-urllib
Description: urllib.request.urlopen(...) is invoked with a URL containing request-derived data, so the standard-library HTTP client is steered to any address the attacker supplies. Unlike requests, urlopen also honours the file://, ftp://, and gopher:// schemes, broadening the SSRF impact to local file disclosure. Validate the parsed URL against a scheme/host allow-list (only http/https, only approved domains) before opening it, and refuse responses from RFC1918 addresses.
CWE: CWE-918 · OWASP: A10:2021, A01:2025
String Field Must Set Null True
Rule ID: string-field-must-set-null-true
Description: When a CharField or TextField combines unique=True with blank=True but omits null=True, every blank submission stores "" and the second blank row violates the unique constraint. Add null=True so empty inputs are persisted as NULL, which UNIQUE treats as distinct values.
Subprocess Injection
Rule ID: subprocess-injection
Description: A taint path connects the Django request object to a subprocess.* call that runs either a dynamically-built command line or one whose first element is a shell (sh, bash, zsh) or interpreter (python). Attacker-supplied arguments can therefore start additional commands and achieve remote code execution. Build the argv list from fixed string literals and map user input to one of a small set of pre-approved operations selected from a dictionary.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Tainted SQL String
Rule ID: tainted-sql-string
Description: A string literal beginning with a SQL keyword (SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP) is concatenated, formatted, or f-stringed with values that taint analysis traces back to the Django request. Whatever sink eventually receives this string (cursor execute, raw, RawSQL, ...) will run attacker-controlled SQL. Prefer Django's ORM (Model.objects.filter(...)); when raw SQL is unavoidable, keep %s placeholders in the literal and pass values through params=[...].
CWE: CWE-915 · OWASP: A08:2021, A08:2025
Tainted URL Host
Rule ID: tainted-url-host
Description: Request data flows into the host segment of a URL being assembled via %, str.format, +=, or an f-string (the literal ends with ://). Whoever calls the view chooses the target server of any subsequent outbound request, enabling SSRF as well as credential leakage if cookies or Authorization headers are forwarded. Hard-code the host or resolve it through a whitelist keyed by an internal identifier rather than concatenating user input directly.
CWE: CWE-918 · OWASP: A10:2021, A01:2025
Template Autoescape Off
Rule ID: template-autoescape-off
Description: Autoescaping is turned off inside this {% autoescape off %} block, so every variable rendered between the tags is emitted as raw HTML. Any user-controlled value passing through here becomes a cross-site scripting (XSS) sink. Remove the directive and rely on Django's default escaping, or constrain trusted strings with mark_safe in the view instead.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Template Blocktranslate No Escape
Rule ID: template-blocktranslate-no-escape
Description: Output emitted by {% blocktranslate %} / {% blocktrans %} bypasses HTML escaping, so a translator (or anyone editing the .po catalog) can inject <script> tags that execute in users' browsers. Wrap the block in {% filter force_escape %} or apply |force_escape to each interpolated placeholder so translated content is treated as untrusted text.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Template Translate As No Escape
Rule ID: template-translate-as-no-escape
Description: A translation captured via {% translate ... as VAR %} (or its {% trans %} alias) is later rendered with {{ VAR }} without escaping, so malicious markup inserted by a translator will execute in the browser. Pipe the variable through |force_escape at the render site, or wrap the output in a {% filter force_escape %} block to neutralize translated HTML.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Template Var Unescaped With Safeseq
Rule ID: template-var-unescaped-with-safeseq
Description: Applying the |safeseq filter marks every item of an iterable as safe HTML, so each element is rendered verbatim without escaping. Should any element originate from user input, the template becomes a cross-site scripting (XSS) vector. Drop the filter and let Django escape items by default, or sanitize trusted strings server-side with mark_safe before rendering.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Unvalidated Password
Rule ID: unvalidated-password
Description: Invoking $MODEL.set_password(...) without first running django.contrib.auth.password_validation.validate_password(...) lets weak, common, or numeric-only credentials reach the user store, defeating the password policies configured in AUTH_PASSWORD_VALIDATORS. Validate the new password (passing the user instance so similarity checks can run) before persisting it, and propagate any ValidationError back to the caller.
CWE: CWE-521 · OWASP: A07:2021, A07:2025
Use Count Method
Rule ID: use-count-method
Description: Counting rows by calling .len() on a QuerySet forces Django to evaluate the query, fetch every matching row, and instantiate model objects in Python just to measure the list. Use QuerySet.count() instead so the database returns a single SELECT COUNT(*) value without hydrating any objects.
Use Decimalfield For Money
Rule ID: use-decimalfield-for-money
Description: Storing monetary value $F in a FloatField introduces binary floating-point rounding errors that accumulate across arithmetic and aggregation, producing incorrect totals and ledger discrepancies. Replace FloatField with DecimalField(max_digits=..., decimal_places=...) so currency is stored as exact base-10 numbers.
Use Earliest Or Latest
Rule ID: use-earliest-or-latest
Description: Combining QuerySet.order_by(...) with [0] instructs the database to sort the full result set and then materialise it just to read the first row, which is wasteful and easy to misread. Switch to QuerySet.earliest("field") or QuerySet.latest("field"), which emit a single-row query and convey intent.
Use None For Password Default
Rule ID: use-none-for-password-default
Description: The argument feeding $MODEL.set_password(...) defaults to "" - either as a function parameter default or as the fallback value of request.GET.get(..., "") - so whenever the caller omits a password Django hashes the empty string and stores it as a valid credential. Use None (or omit the fallback so a missing field raises an error) and call $MODEL.set_unusable_password() when you explicitly want to disable password authentication.
CWE: CWE-521 · OWASP: A07:2021, A07:2025
User Eval
Rule ID: user-eval
Description: A value taken straight from the Django request object is passed to eval, which executes it as a Python expression and grants the caller arbitrary code execution under the server process. There is no safe way to sandbox eval against attacker-controlled input. Parse the value explicitly with int(...), decimal.Decimal(...), or ast.literal_eval, depending on what the field is supposed to contain.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
User Eval Format String
Rule ID: user-eval-format-string
Description: Request data is woven into a formatted string (%, str.format, or f-string) and then handed to eval, so an attacker controls part of the Python expression being evaluated. Even with surrounding literal text the injected value can close the expression and run arbitrary code. Drop eval entirely and use ast.literal_eval for safe literals or a parser tailored to the input you expect.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
User Exec
Rule ID: user-exec
Description: Request data flows directly into exec (sometimes via loop.run_in_executor(None, exec, ...) for async views), letting a caller execute arbitrary Python statements with the privileges of the Django process. exec cannot be safely sandboxed against untrusted input. Eliminate it and route the requested behaviour through declared view functions selected by an explicit mapping.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
User Exec Format String
Rule ID: user-exec-format-string
Description: A request-derived value (optionally decoded through base64.decodestring) is concatenated via %, str.format, or an f-string and then passed to exec, which runs the resulting text as a full Python statement block. The base64 wrapping provides no protection: it merely obfuscates the payload before execution. Remove the exec call and dispatch to vetted Python functions chosen by a strict allow-list.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
XSS HTML Email Body
Rule ID: xss-html-email-body
Description: A request value is used as the body of a django.core.mail.EmailMessage whose content_subtype is set to "html", so any HTML or JavaScript the client provides is delivered to the recipient as live markup. Mail clients that render HTML will execute embedded scripts (XSS) or reveal exfiltration payloads. Render the body through a Django template with autoescaping on, or sanitize the input with bleach.clean(...) before assigning it.
CWE: CWE-74 · OWASP: A03:2021, A05:2025
XSS Send Mail HTML Message
Rule ID: xss-send-mail-html-message
Description: django.core.mail.send_mail is called with the html_message= keyword bound to data taken from the current request, so an attacker can supply the HTML body of the outbound email. Mail clients that render HTML will execute scripts, fetch tracking pixels, or display spoofed UI to the recipient. Build the HTML payload from a trusted template via render_to_string, or escape user-controlled fragments before interpolating them.
CWE: CWE-74 · OWASP: A03:2021, A05:2025
Docker Arbitrary Container Run
Rule ID: docker-arbitrary-container-run
Description: A non-literal image name is being passed to docker.client.containers.run(...) or containers.create(...) on a DockerClient / docker.from_env() handle. If the image identifier originates from user input, an attacker can pull and launch an arbitrary container, effectively gaining code execution on the host. Pin container images to a vetted allowlist of constant references.
CWE: CWE-250
Wildcard CORS
Rule ID: wildcard-cors
Description: Permissive CORSMiddleware(allow_origins=["*"]) configuration grants any origin permission to call this FastAPI app, which exposes authenticated endpoints to cross-site attacks. Replace the wildcard with an explicit allowlist of trusted origins so browsers enforce same-origin restrictions properly.
CWE: CWE-942 · OWASP: A05:2021, A02:2025
Avoid App Run With Bad Host
Rule ID: avoid_app_run_with_bad_host
Description: Binding the Flask development server to 0.0.0.0 via app.run(host="0.0.0.0") makes it reachable on every network interface, including public ones. Bind to a specific internal address (e.g. 127.0.0.1) for local development, and put production traffic behind a real WSGI server such as gunicorn or uWSGI.
CWE: CWE-668 · OWASP: A01:2021, A01:2025
Avoid Hardcoded Config DEBUG
Rule ID: avoid_hardcoded_config_DEBUG
Description: Pinning app.config["DEBUG"] in source code can leave the Werkzeug debugger turned on in production, exposing an interactive Python REPL over tracebacks. Read the value from the FLASK_DEBUG environment variable instead so it can be safely toggled per deployment.
CWE: CWE-489 · OWASP: A05:2021, A02:2025
Avoid Hardcoded Config ENV
Rule ID: avoid_hardcoded_config_ENV
Description: The Flask ENV setting controls debug, error handling, and middleware selection, so pinning it to "development" or "production" in code couples the environment to a specific deployment. Drive it from the standard FLASK_ENV environment variable so the same artifact can run in any environment.
CWE: CWE-489 · OWASP: A05:2021, A02:2025
Avoid Hardcoded Config SECRET KEY
Rule ID: avoid_hardcoded_config_SECRET_KEY
Description: Embedding a literal SECRET_KEY in app.config checks the session-signing secret into version control, allowing anyone with repo access to forge Flask session cookies and CSRF tokens. Load SECRET_KEY from an environment variable or secret manager and rotate the leaked value immediately.
CWE: CWE-489 · OWASP: A05:2021, A02:2025
Avoid Hardcoded Config TESTING
Rule ID: avoid_hardcoded_config_TESTING
Description: Hardcoding TESTING in app.config baked the testing flag into source control, which can ship test-only behaviour to production or vice versa. Source the value from a FLASK_TESTING environment variable or a config object loaded via Flask.config.from_envvar instead.
CWE: CWE-489 · OWASP: A05:2021, A02:2025
Avoid Send File Without Path Sanitization
Rule ID: avoid_send_file_without_path_sanitization
Description: A route parameter named filename is being passed directly to flask.send_file(...), so an attacker can request ../../etc/passwd (or any other host file) and read it through the response. Use flask.send_from_directory(safe_base, filename) which performs the path-confinement check, and validate the filename component first.
CWE: CWE-73 · OWASP: A04:2021, A06:2025
Avoid Using App Run Directly
Rule ID: avoid_using_app_run_directly
Description: Calling app.run(...) at module top level executes whenever the module is imported (e.g. by a WSGI server), which can spawn the development server in production. Wrap it inside if __name__ == "__main__": or a function so it only runs when the script is invoked directly.
CWE: CWE-668 · OWASP: A01:2021, A01:2025
Avoid Accessing Request In Wrong Handler
Rule ID: avoid-accessing-request-in-wrong-handler
Description: Reading flask.request.json, flask.request.form, or flask.request.data inside a GET route handler will raise at runtime because GET requests have no body. Move the body access to a handler bound to POST/PUT/etc., or use flask.request.args for query-string parameters.
Csv Writer Injection
Rule ID: csv-writer-injection
Description: Values taken from flask.request.* (forms, args, headers, JSON body or route variables) flow into csv.writer().writerow(...)/writerows(...) without sanitisation. When opened in a spreadsheet, fields starting with =, +, -, @ are interpreted as formulas, enabling CSV/formula injection. Prefix risky leading characters with a single quote or switch to the drop-in defusedcsv writer.
CWE: CWE-1236 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Template String
Rule ID: dangerous-template-string
Description: A string built through .format(), %, +=, or an f-string is being passed to flask.render_template_string(...). Because the template source itself is dynamic, anything embedded in it is interpreted by Jinja2 and can lead to server-side template injection (SSTI) or stored XSS. Move the template to a static file rendered with flask.render_template() and pass only the variable values as context.
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Debug Enabled
Rule ID: debug-enabled
Description: Starting Flask with app.run(debug=True) enables the Werkzeug interactive debugger, which exposes a remote Python REPL on tracebacks and leaks stack frames to the browser. Read the flag from FLASK_DEBUG or a config object so production deployments stay disabled.
CWE: CWE-489 · OWASP: A06:2017
Direct Use Of Jinja2
Rule ID: direct-use-of-jinja2
Description: Rendering through raw jinja2.Environment or jinja2.Template.render(...) skips the safe defaults that Flask applies on top of Jinja2, leaving the response open to cross-site scripting whenever a template variable carries untrusted data. Route HTML generation through flask.render_template(...) with a .html file so autoescaping stays on by extension.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Directly Returned Format String
Rule ID: directly-returned-format-string
Description: Returning an f-string, .format() result, % interpolation, or string concatenation from a Flask view sends raw HTML to the browser without autoescaping. If any taint from flask.request reaches the formatted value, it becomes a stored or reflected XSS sink. Render through flask.render_template() with .html templates so Jinja escapes the interpolated values.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Eval Injection
Rule ID: eval-injection
Description: Tainted data from flask.request.* reaches eval(...), which evaluates the value as a Python expression and gives the caller direct remote code execution on the server. Remove the eval call entirely; if structured input is required, parse it with ast.literal_eval or json.loads.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Exec Injection
Rule ID: exec-injection
Description: flask.request data is passed to exec(...), which runs the argument as arbitrary Python statements - the attacker effectively gains full code execution on the server. Drop the exec call; switch to data-driven dispatch (e.g. a function lookup keyed on a validated string) when runtime behaviour really must depend on the request.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Explicit Unescape With Markup
Rule ID: explicit-unescape-with-markup
Description: Wrapping a dynamic value with flask.Markup(...) (or calling .unescape() on a Markup instance) tells Jinja2 the string is already safe and disables HTML escaping for that fragment, which becomes XSS the moment the value is influenced by a user. Confirm the argument is a hard-coded literal, otherwise drop the Markup wrapper and let autoescaping handle the content.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Flask API Method String Format
Rule ID: flask-api-method-string-format
Description: The Flask-RESTful controller $CLASS.$METHOD formats argument $ARG directly into the URL passed to requests.$REQMETHOD(...), letting a client steer the outbound HTTP call. Validate $ARG against a strict allowlist or pass dynamic data through params=/json= instead of interpolating it into the URL string.
CWE: CWE-134
Flask Cache Query String
Rule ID: flask-cache-query-string
Description: Decorating a handler that reads request.args with @cache.cached(...) without query_string=True will return stale responses because Flask-Caching ignores the query string by default. Additionally, mutating verbs (POST, PUT, DELETE, PATCH) should never be cached. Pass query_string=True and restrict caching to safe HTTP methods.
Flask CORS Misconfiguration
Rule ID: flask-cors-misconfiguration
Description: Combining origins="*" with supports_credentials=True on CORS(...) or @cross_origin(...) makes Flask-CORS reflect every request's Origin header back as Access-Control-Allow-Origin while also sending cookies and Authorization headers, which lets any site read authenticated responses. Replace the wildcard with an explicit list of trusted origins.
CWE: CWE-942 · OWASP: A07:2021, A07:2025
Flask Deprecated Apis
Rule ID: flask-deprecated-apis
Description: Call targets a Flask API removed or deprecated in modern releases (e.g. open_session, save_session, make_null_session, init_jinja_globals, request_globals_class, static_path, config.from_json, flask.json_available, flask.request.module, flask.testing.make_test_environ_builder). Replace it with the supported equivalent so the app keeps working on current Flask versions.
Flask Duplicate Handler Name
Rule ID: flask-duplicate-handler-name
Description: Two @app.route(...) decorators register a view named $R, but Flask requires unique endpoint names and will raise AssertionError: View function mapping is overwriting an existing endpoint function at startup. Rename one of the handlers or supply an explicit endpoint= argument on the decorator.
Flask URL For External True
Rule ID: flask-url-for-external-true
Description: Building absolute URLs via flask.url_for(..., _external=True) reuses the incoming Host header, so an attacker who controls that header can poison password-reset links, callback URLs, and similar artefacts. Either set _external=False, configure SERVER_NAME so the framework uses a known hostname, or validate the Host header upstream.
CWE: CWE-673 · OWASP: A03:2021, A05:2025
Flask Wtf CSRF Disabled
Rule ID: flask-wtf-csrf-disabled
Description: Assigning WTF_CSRF_ENABLED = False on the Flask config switches off Flask-WTF's CSRF protection for every form and JSON POST handled by the app, leaving state-changing endpoints exposed to cross-site requests. Remove the override (or restrict it to test-only configs) and keep CSRF tokens validated by default.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
Hashids With Flask Secret
Rule ID: hashids-with-flask-secret
Description: Passing app.config['SECRET_KEY'] as the salt for hashids.Hashids(...) leaks Flask's session-signing secret: HashIDs uses a reversible encoding, so an attacker that gathers enough generated IDs can recover the salt and therefore the Flask secret. Use a dedicated random salt for HashIDs and keep SECRET_KEY exclusive to Flask session and CSRF signing.
CWE: CWE-327 · OWASP: A02:2021
Host Header Injection Python
Rule ID: host-header-injection-python
Description: Concatenating flask.request.host into an HTTP URL trusts an attacker- controlled header to build that URL. The resulting request can be steered at internal services (SSRF), used to bypass password-reset flows, or weaponized for cache poisoning. Validate the host against an allowlist or use a fixed SERVER_NAME from configuration before constructing URLs.
CWE: CWE-20
Insecure Deserialization
Rule ID: insecure-deserialization
Description: A Flask route is invoking pickle.load, cPickle.load, dill.load, shelve.open, or yaml.load on data that may come from the client. These deserializers execute embedded gadget chains, so an attacker can achieve remote code execution simply by submitting a crafted payload. Switch to a safe format such as JSON, or use yaml.safe_load/signed formats with strict schemas.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Make Response With Unknown Content
Rule ID: make-response-with-unknown-content
Description: Passing arbitrary content into flask.make_response(...) bypasses Jinja's autoescape, so any unescaped HTML can become a cross-site scripting sink. Build HTML via flask.render_template() against a .html template, return JSON through flask.jsonify(), or wrap a known-safe redirect/template result before handing it to make_response.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Nan Injection
Rule ID: nan-injection
Description: Casting a flask.request value through float(...), bool(...) or complex(...) lets a client submit the literal strings "nan" or "inf", which become Python NaN/infinity and break comparisons, sorts, and arithmetic in unpredictable ways. Reject nan/inf explicitly (e.g. math.isfinite) or parse via int(...) when a finite integer is what the handler actually needs.
CWE: CWE-704
Open Redirect
Rule ID: open-redirect
Description: A value from flask.request.* flows into flask.redirect(...), giving a client full control over the Location header. This open redirect can be abused for phishing and OAuth/SSO callback hijacking. Build the target with flask.url_for(endpoint, ...) or check that urlparse(target).netloc matches the current host before redirecting.
CWE: CWE-601 · OWASP: A01:2021, A01:2025
OS System Injection
Rule ID: os-system-injection
Description: Data from a Flask request.args/request.form/route argument is being concatenated into an os.system(...) call, which executes through /bin/sh and is fully susceptible to shell metacharacters. Replace it with subprocess.run(["cmd", arg1, ...], shell=False) so arguments are passed as a list, or strictly validate the inputs against an allowlist.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Path Traversal Open
Rule ID: path-traversal-open
Description: A Flask request value reaches open(...) as the file path, allowing ../ sequences or absolute paths to escape the intended directory (path traversal / arbitrary file read or write). Resolve the path with os.path.realpath() and verify it stays inside an allowed base directory, or use flask.send_from_directory(...) which performs this check for downloads.
CWE: CWE-22 · OWASP: A05:2017, A01:2021, A01:2025
Raw HTML Format
Rule ID: raw-html-format
Description: Hand-rolled HTML strings (built via +, %, .format(), or f-strings) contain unescaped data drawn from flask.request.*, which sidesteps Jinja's autoescape and creates a reflected XSS sink. Render through flask.render_template('page.html', ...), or sanitize the value with markupsafe.escape(...) before concatenating.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Render Template String
Rule ID: render-template-string
Description: Using flask.render_template_string(...) evaluates the supplied template text through Jinja2, so any user-controlled input concatenated into that string can break out into the template language and trigger server-side template injection (SSTI) leading to RCE. Render a static .html template via flask.render_template() and pass user data as context variables.
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Response Contains Unsanitized Input
Rule ID: response-contains-unsanitized-input
Description: Values pulled from request.args.get(...) are forwarded into flask.make_response(...) without escaping, allowing attacker-controlled markup to be reflected back to the browser as part of the response body. Render the output through a Jinja2 template (for example via render_template) so Flask's autoescaping neutralises the payload before it reaches the client.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Secure Set Cookie
Rule ID: secure-set-cookie
Description: $RESP.set_cookie(...) on a Flask Response/make_response object omits the secure, httponly, and samesite arguments, so the cookie defaults to being sent over plaintext HTTP, readable from JavaScript, and attached on cross-site requests. Pass secure=True, httponly=True, samesite='Lax' explicitly, or set the equivalent SESSION_COOKIE_* keys in the Flask configuration.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
SSRF Requests
Rule ID: ssrf-requests
Description: A value from flask.request is forwarded into requests.get/post/put/..., letting a client steer the outbound HTTP request at internal services (metadata endpoints, intranet hosts) for server-side request forgery. Parse the URL and confirm the scheme and host are on an allowlist before issuing the request, and never echo the response straight back to the caller.
CWE: CWE-918 · OWASP: A10:2021, A01:2025
Subprocess Injection
Rule ID: subprocess-injection
Description: Tainted data from flask.request.* reaches subprocess.$FUNC(...) either as the dynamic program path or inside a shell invocation (["sh", "-c", ...], ["python", ...]), which lets a client smuggle arbitrary commands. Pass a fixed argv list with shell=False, or map request data through a dictionary that whitelists the allowed commands.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Tainted SQL String
Rule ID: tainted-sql-string
Description: A SELECT/INSERT/UPDATE/DELETE statement is being stitched together via +, %, .format(), or an f-string using data from flask.request, which is the textbook recipe for SQL injection. Use parameterized queries (cursor.execute(sql, params) or SQLAlchemy text(...).bindparams(...)) so the driver, not the formatter, escapes the values.
CWE: CWE-704 · OWASP: A01:2017, A03:2021, A05:2025
Tainted URL Host
Rule ID: tainted-url-host
Description: A flask.request value is interpolated into the host portion of a URL (after ://) via +, %, .format(), or an f-string. Anything that consumes the resulting URL (outbound HTTP, redirect, link in a response) can then be steered at attacker-controlled or internal hosts, enabling SSRF or credential leakage. Keep the host fixed or look it up from an allowlist keyed by safe identifiers.
CWE: CWE-918 · OWASP: A10:2021, A01:2025
Template Autoescape Off
Rule ID: template-autoescape-off
Description: An {% autoescape false %} block opens a section of the Flask template where Jinja2 emits raw HTML for every expression inside it. Any variable that reaches this region is no longer escaped, so attacker-controlled markup will execute in the rendered page. Either remove the block or scope it tightly to constants you fully control.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Template Unescaped With Safe
Rule ID: template-unescaped-with-safe
Description: Piping a template expression through the | safe filter marks the value as pre-escaped HTML and tells Jinja2 to emit it verbatim. When the value originates from request data, this becomes a stored or reflected XSS sink. Drop the filter so autoescape applies, or convert the value to a trusted constant before rendering.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Template Unquoted Attribute Var
Rule ID: template-unquoted-attribute-var
Description: An HTML attribute is assigned a {{ request.* }} value without surrounding quotes, so an attacker can break out of the attribute boundary and attach event handlers such as onmouseover=.... Always wrap dynamic attribute values in double quotes (="{{ var }}") so attribute-context escaping is effective.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Unescaped Template Extension
Rule ID: unescaped-template-extension
Description: flask.render_template(...) only turns on Jinja autoescape for templates whose names end in .html, .htm, .xml, or .xhtml. Rendering a file with any other extension (or an extension chosen at runtime) ships unescaped output to the browser and creates a cross-site scripting risk. Rename the template to use a recognised extension, or call select_autoescape explicitly on the Jinja environment.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Incorrect Autoescape Disabled
Rule ID: incorrect-autoescape-disabled
Description: jinja2.Environment(...) is constructed with an explicit autoescape value that is neither True nor jinja2.select_autoescape(...), which means HTML in rendered variables will be emitted verbatim. For any template that ends up in a browser this is an XSS sink. Pass autoescape=True, or use jinja2.select_autoescape(...) to opt in by extension.
CWE: CWE-116 · OWASP: A03:2021, A05:2025
Missing Autoescape Disabled
Rule ID: missing-autoescape-disabled
Description: No autoescape keyword is supplied to jinja2.Environment(...), so the constructor falls back to its insecure default of False and every variable is rendered as raw HTML. Add autoescape=True when instantiating the environment, or pass jinja2.select_autoescape(["html", "xml"]) to enable escaping for the file types that need it.
CWE: CWE-116 · OWASP: A03:2021, A05:2025
JWT Python Exposed Credentials
Rule ID: jwt-python-exposed-credentials
Description: A "password" key is being embedded in a payload handed to jwt.encode(...). JWT claims are merely signed and base64 encoded, so anyone with the token can read the plaintext credential. Remove the password from the claim set and reference the user by an opaque identifier instead.
CWE: CWE-522 · OWASP: A02:2017, A04:2021, A06:2025
JWT Python Exposed Data
Rule ID: jwt-python-exposed-data
Description: A function parameter flows directly into jwt.encode(...) as the payload, meaning whatever the caller passes in becomes part of a token that is only base64 encoded, not encrypted. Audit the payload construction and strip any PII, internal identifiers, or secret material before signing.
CWE: CWE-522 · OWASP: A02:2017, A04:2021, A06:2025
JWT Python Hardcoded Secret
Rule ID: jwt-python-hardcoded-secret
Description: The signing secret passed to jwt.encode(payload, "...", ...) is a string literal, so anyone with read access to the source repository can mint or tamper with tokens. Load the secret from a runtime configuration source (environment variables, a secret manager, etc.) so it can be rotated without code changes.
CWE: CWE-522 · OWASP: A02:2017, A04:2021, A06:2025
JWT Python None Alg
Rule ID: jwt-python-none-alg
Description: Either jwt.encode(..., algorithm="none", ...) or jwt.decode(..., algorithms=[..., "none", ...], ...) is in use here. The none algorithm produces an unsigned token, so any attacker can forge a payload that the verifier still treats as authentic. Restrict the accepted algorithms list to strong schemes such as HS256, RS256, or EdDSA.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Unverified JWT Decode
Rule ID: unverified-jwt-decode
Description: jwt.decode(...) is being invoked with options={"verify_signature": False}, so the library returns the embedded claims without checking the cryptographic signature. Any attacker who can submit a token to this endpoint can therefore impersonate any user. Drop the override (or set the flag to True) so signatures are always verified.
CWE: CWE-287 · OWASP: A02:2017, A07:2021, A07:2025
Aiopg Sqli
Rule ID: aiopg-sqli
Description: The query passed to aiopg cursor execute is built from string concatenation, .format, % formatting, or an f-string with non-literal values. Any tainted segment becomes a SQL injection vector. Use the driver's parameter binding instead, e.g. await cur.execute("SELECT %s FROM table", (user_value,)), so the value is escaped by the driver.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Asyncpg Sqli
Rule ID: asyncpg-sqli
Description: A dynamically assembled SQL string is being handed to an asyncpg.Connection method (fetch, execute, prepare, etc.). When the interpolated value comes from a request it can carry a SQL injection payload. Switch to asyncpg's positional placeholders -- await conn.fetch("SELECT $1 FROM table", value) -- or build a prepared statement via Connection.prepare.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Avoid Bind To All Interfaces
Rule ID: avoid-bind-to-all-interfaces
Description: The socket is being bound to 0.0.0.0, ::, or an empty host string, which causes the listener to accept connections on every network interface — including ones reachable from the public internet or untrusted VPCs. Bind to a specific loopback or private-network address (e.g. 127.0.0.1 for local-only services) and read the value from configuration rather than hardcoding it.
CWE: CWE-200 · OWASP: A01:2021, A01:2025
Avoid CPickle
Rule ID: avoid-cPickle
Description: cPickle is the legacy Python 2 alias for the C implementation of pickle and inherits the same attack surface: a tainted blob passed to cPickle.load or cPickle.loads executes opcodes that can call any importable Python callable. Migrate to json (or a binary format with a fixed schema such as Protocol Buffers) for any data crossing a trust boundary.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Dill
Rule ID: avoid-dill
Description: A dill.$FUNC call is being applied to non-literal data. dill extends pickle to handle closures and lambdas, but it preserves the same __reduce__-based execution model, so loading a hostile blob still grants arbitrary code execution. Prefer JSON, MessagePack with a registry, or another data-only format for transferring values between trust zones.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Jsonpickle
Rule ID: avoid-jsonpickle
Description: jsonpickle.decode is being invoked on non-literal data. The library reconstructs Python objects from the JSON envelope, including calling arbitrary class constructors, so an attacker who controls the payload can achieve remote code execution. Restrict deserialization to the standard library json module and rebuild domain objects manually from the parsed primitives.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Pickle
Rule ID: avoid-pickle
Description: A pickle.$FUNC (or _pickle.$FUNC) call is operating on non-literal data. The pickle wire format encodes REDUCE opcodes that invoke arbitrary callables during pickle.loads, which is the canonical Python remote code execution sink. Serialize with json.dumps or another schema-driven text format and validate the structure on load.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Pyyaml Load
Rule ID: avoid-pyyaml-load
Description: PyYAML is being invoked with an unsafe loader: yaml.unsafe_load, or yaml.load/yaml.load_all parameterised with yaml.Loader, yaml.UnsafeLoader, or yaml.CLoader. Those loaders honour the !!python/ tag family and instantiate arbitrary classes, which lets a crafted document execute Python at load time. Use yaml.safe_load (or Loader=yaml.SafeLoader) for any input that is not fully under your control.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Shelve
Rule ID: avoid-shelve
Description: The shelve module persists values by calling pickle under the hood, so reading a tampered shelf file is equivalent to running pickle.loads on attacker-controlled bytes and yields arbitrary code execution. Store structured data in a SQL-backed database or a JSON document keyed by an authenticated identifier instead.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Avoid Unsafe Ruamel
Rule ID: avoid-unsafe-ruamel
Description: Instantiating ruamel.yaml.YAML with typ='unsafe' or typ='base' activates a loader that can materialize arbitrary Python objects from !!python/... tags, turning a tainted document into a code-execution primitive. Configure the parser with typ='safe' (or typ='rt' for round-trip needs) so only standard YAML scalars and collections are produced.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
Baseclass Attribute Override
Rule ID: baseclass-attribute-override
Description: Class $C inherits from both $A and $B, and each base defines its own $F, so Python's MRO will resolve $F to exactly one of them and the other implementation is effectively hidden. Rename the conflicting method on one base, override $F explicitly in $C to delegate to both, or use composition instead of multiple inheritance to make the resolution intentional.
Cannot Cache Generators
Rule ID: cannot-cache-generators
Description: Decorating a generator function with @functools.lru_cache caches the generator object itself, not its values, so the second call returns the same already-exhausted iterator and iteration yields nothing. Either drop the cache, eagerly materialise the results into a list/tuple inside the cached function, or use functools.cache over a non-generator wrapper.
Dangerous Annotations Usage
Rule ID: dangerous-annotations-usage
Description: Writing arbitrary expressions into $C.__annotations__ is risky because typing.get_type_hints later evaluates those annotations as Python code against globals and locals, which turns an attacker-controlled annotation string into eval injection. Restrict annotations to literal types or whitelisted typing.* constructs and never assign untrusted values into __annotations__.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Asyncio Create Exec Audit
Rule ID: dangerous-asyncio-create-exec-audit
Description: A call to asyncio.create_subprocess_exec (or asyncio.subprocess.create_subprocess_exec) is being made with a non-literal program or argument vector. When any of those values are influenced by an attacker the spawned coroutine becomes a command-injection sink. Validate inputs against an allowlist and pass already-tokenized arguments, escaping any dynamic shell payload with shlex.quote.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Create Exec Tainted Env Args
Rule ID: dangerous-asyncio-create-exec-tainted-env-args
Description: Taint flow reaches asyncio.create_subprocess_exec (or asyncio.subprocess.create_subprocess_exec) from CLI arguments or environment variables (sys.argv, os.environ, argparse, getopt, etc.), which lets the caller inject OS commands. Treat all such inputs as untrusted: validate against a fixed allowlist and quote dynamic fragments with shlex.quote before passing them as arguments.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Exec Audit
Rule ID: dangerous-asyncio-exec-audit
Description: The event-loop API loop.subprocess_exec is being invoked with a dynamically built program path or argument list. Because the loop forwards each argument straight to the OS, any attacker-controlled component becomes an OS command-injection vector. Restrict the program path to a fixed value and sanitize argument fragments with shlex.quote before they reach the call.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Exec Tainted Env Args
Rule ID: dangerous-asyncio-exec-tainted-env-args
Description: User-controlled data (CLI flags, os.environ, argparse/getopt output) flows directly into loop.subprocess_exec, producing an OS command-injection sink on the asyncio event loop. Constrain the program path to a hardcoded value and wrap any caller-supplied argument with shlex.quote, or refuse the value if it does not match a strict allowlist.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Shell Audit
Rule ID: dangerous-asyncio-shell-audit
Description: Building the command string passed to asyncio.create_subprocess_shell (or loop.subprocess_shell) dynamically forwards the result to /bin/sh -c, so any concatenated input becomes a shell metacharacter primitive. Prefer the argv-form asyncio.create_subprocess_exec with a fixed program, or — if a shell is unavoidable — assemble the command from shlex.quote-escaped fragments only.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Asyncio Shell Tainted Env Args
Rule ID: dangerous-asyncio-shell-tainted-env-args
Description: Tainted CLI or environment input flows into asyncio.create_subprocess_shell/loop.subprocess_shell, which executes the resulting string through /bin/sh -c and exposes a full shell command-injection surface. Switch to the argv-based create_subprocess_exec API with a hardcoded program, or escape every untrusted fragment with shlex.quote before concatenating it into the command.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Globals Use
Rule ID: dangerous-globals-use
Description: A non-literal key is being used to index globals(), locals(), or func.__globals__. Treating that dictionary as a lookup table lets an attacker who controls the key resolve any module-level callable -- including eval, exec, or __import__ -- and execute arbitrary code. Replace the dynamic dispatch with an explicit allowlist mapping of names to functions.
CWE: CWE-96 · OWASP: A03:2021, A05:2025
Dangerous Interactive Code Run
Rule ID: dangerous-interactive-code-run
Description: Tainted request data flows into code.InteractiveConsole.push, InteractiveInterpreter.runsource, or runcode(code.compile_command(...)). Any of these helpers compile and execute the supplied string as Python, giving an attacker arbitrary code execution. Never feed untrusted input to the code module -- restrict it to operator-only REPL surfaces.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Interactive Code Run Audit
Rule ID: dangerous-interactive-code-run-audit
Description: Feeding a non-literal payload into code.InteractiveConsole.push, InteractiveInterpreter.runsource, or runcode(code.compile_command(...)) will compile and execute that string as Python in the current process, giving any attacker that controls the input full code-execution. Treat these REPL APIs as privileged: hardcode the payload or gate it behind a strict allowlist before invoking the call.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Interactive Code Run Tainted Env Args
Rule ID: dangerous-interactive-code-run-tainted-env-args
Description: User-supplied input (from sys.argv, os.environ, argparse, or getopt) reaches code.InteractiveConsole.push/InteractiveInterpreter.runsource/runcode, where it is compiled and executed as Python — a direct path to remote code execution. Remove the dynamic source or enforce a strict allowlist of fixed snippets before invoking these REPL entry points.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous OS Exec
Rule ID: dangerous-os-exec
Description: Tainted input is being passed into os.execl*/os.execv* -- either as the program path or as the argument vector to a shell wrapped via sh -c. Because these calls replace the current process with the supplied command, attacker-supplied content yields OS command injection. Hard-code the executable path and pass user data only as discrete, validated argv elements (never through -c).
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous OS Exec Audit
Rule ID: dangerous-os-exec-audit
Description: A os.exec* family call (os.execv, os.execve, os.execlp, etc.) is being invoked with a dynamic program path or -c shell payload. os.exec* replaces the current process image with whatever the arguments point to, so any non-literal value here is a high-impact command-injection sink. Restrict the program path to a constant and quote shell payloads with shlex.quote before they reach the call.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous OS Exec Tainted Env Args
Rule ID: dangerous-os-exec-tainted-env-args
Description: Tainted CLI/environment input flows into an os.exec* call (os.execv, os.execve, os.execlp, …), which replaces the current process with whatever the attacker specifies — a direct OS command-injection vector. Reject the input or constrain it via an allowlist, and pass shell payloads only after escaping them with shlex.quote.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Spawn Process
Rule ID: dangerous-spawn-process
Description: User-controlled data is flowing into os.spawn*, os.posix_spawn*, or os.startfile, including the -c payload of shell-prefixed invocations. These functions launch a child process with the provided command, so tainted arguments translate directly into OS command injection. Resolve the binary path statically and forward untrusted values only as separate, validated arguments.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Spawn Process Audit
Rule ID: dangerous-spawn-process-audit
Description: os.spawn*, os.posix_spawn, or os.startfile is being called with a non-literal program or -c payload. These APIs launch external processes with the supplied arguments verbatim, so any dynamic fragment can be turned into a command-injection primitive. Hardcode the program path and quote untrusted shell payloads with shlex.quote before spawning.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Spawn Process Tainted Env Args
Rule ID: dangerous-spawn-process-tainted-env-args
Description: Untrusted CLI/environment data is reaching os.spawn*/os.posix_spawn/os.startfile, all of which launch external processes with the arguments you supply. That gives a caller arbitrary command execution rights on the host. Validate input against an allowlist, hardcode the program path, and quote shell payloads with shlex.quote before spawning.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Subinterpreters Run String
Rule ID: dangerous-subinterpreters-run-string
Description: Tainted input reaches _xxsubinterpreters.run_string, which compiles and executes the supplied source inside a fresh subinterpreter. The subinterpreter still shares the host process's privileges, so attacker code runs with full application permissions. Never feed external strings into this CPython internal API.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Subinterpreters Run String Audit
Rule ID: dangerous-subinterpreters-run-string-audit
Description: Passing a non-literal source string to _xxsubinterpreters.run_string compiles and runs that text inside a subinterpreter — equivalent to exec() for code-injection purposes. If any caller-controlled value reaches this point the attacker can execute arbitrary Python. Limit the payload to a fixed string or to entries from a strict allowlist, and avoid building the source dynamically.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Subinterpreters Run String Tainted Env Args
Rule ID: dangerous-subinterpreters-run-string-tainted-env-args
Description: Tainted data from sys.argv, os.environ, argparse, or getopt is being fed into _xxsubinterpreters.run_string, which compiles and executes that source inside a subinterpreter. The result is full Python code-execution under attacker control. Refuse to forward such input and keep the payload constant or whitelisted.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Subprocess Use
Rule ID: dangerous-subprocess-use
Description: subprocess.$FUNC is being called with tainted data as either the program, the -c payload for a shell wrapper, or an argument to a python invocation. Each of these paths reaches execve with attacker-controlled bytes, which is a textbook command-injection sink. Pin the binary, drop the shell wrapper, and pass user values as discrete argv entries (escape with shlex.quote if a shell really is unavoidable).
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Subprocess Use Audit
Rule ID: dangerous-subprocess-use-audit
Description: subprocess.$FUNC (Popen, run, call, check_output, …) is being invoked with a non-literal program or -c payload. With dynamic values an attacker can choose what binary runs or smuggle metacharacters through bash -c/python -c. Pin the executable to a constant, prefer the argv-list form, and wrap any caller-supplied fragment with shlex.quote.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Subprocess Use Tainted Env Args
Rule ID: dangerous-subprocess-use-tainted-env-args
Description: Tainted CLI/environment input reaches subprocess.$FUNC, putting attacker data straight into the program path or shell/python -c payload. This is a textbook OS command-injection sink. Either refuse to forward the value, or scope it via an allowlist and wrap dynamic fragments with shlex.quote before passing them as arguments.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous System Call
Rule ID: dangerous-system-call
Description: Request-derived data flows into os.system or one of the os.popen* variants (including obfuscated lookups via getattr or __import__("os")). Both APIs hand the full command string to /bin/sh -c, so any tainted fragment introduces OS command injection. Switch to subprocess.run with a fixed program path and shell=False, passing arguments as a list.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous System Call Audit
Rule ID: dangerous-system-call-audit
Description: os.system (or one of the os.popen* variants) is being called with a non-literal command string. These helpers always spawn /bin/sh -c <cmd>, so concatenating any dynamic value into the argument is a direct shell-injection vector. Switch to subprocess.run with an argv list and a hardcoded executable, and escape any remaining dynamic fragments with shlex.quote.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous System Call Tainted Env Args
Rule ID: dangerous-system-call-tainted-env-args
Description: Tainted CLI/environment input reaches os.system/os.popen/os.popen[2-4], which always wrap the payload in /bin/sh -c. That turns user input into an arbitrary shell command. Replace these calls with subprocess.run([...], shell=False) against a hardcoded executable, and validate or shlex.quote any dynamic argument before passing it in.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Dangerous Testcapi Run In Subinterp
Rule ID: dangerous-testcapi-run-in-subinterp
Description: Tainted data is passed to _testcapi.run_in_subinterp or test.support.run_in_subinterp, both of which compile and execute the string as Python inside a fresh subinterpreter. The new interpreter still runs in the host process, so attacker-supplied code retains the application's privileges. These test-only helpers must never receive untrusted input.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Testcapi Run In Subinterp Audit
Rule ID: dangerous-testcapi-run-in-subinterp-audit
Description: A non-literal source string is being handed to _testcapi.run_in_subinterp (or test.support.run_in_subinterp). These helpers compile and execute the supplied text inside a fresh interpreter, which is functionally equivalent to exec() from an attacker's perspective. Keep the argument literal or restricted to a whitelist of fixed snippets to avoid Python eval injection.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Dangerous Testcapi Run In Subinterp Tainted Env Args
Rule ID: dangerous-testcapi-run-in-subinterp-tainted-env-args
Description: Taint flow shows CLI or environment data reaching _testcapi.run_in_subinterp/test.support.run_in_subinterp, where it is compiled and executed inside a sub-interpreter. That gives the caller arbitrary Python code-execution. Strip the dynamic source — accept only a fixed payload or one drawn from a strict allowlist before invoking these helpers.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Default Mutable Dict
Rule ID: default-mutable-dict
Description: Declaring $D={} as a default argument and then writing to it with $D[...] = ..., $D.update(...), or $D.setdefault(...) is the classic Python mutable-default pitfall: the dict is created once when $F is defined and every later call that omits $D mutates and observes the same shared object, so keys leak across unrelated callers. Use the sentinel pattern def $F(..., $D=None): if $D is None: $D = {} so each invocation starts with a fresh dictionary.
Default Mutable List
Rule ID: default-mutable-list
Description: The default value $D=[] on $F is evaluated a single time when the def statement runs, so calls that rely on the default all reach into the same list; once append, extend, or insert runs, every later caller sees the accumulated items from previous invocations. Default the parameter to None and create a new list inside the body ($D = [] if $D is None else $D) so each call starts with its own.
Dict Del While Iterate
Rule ID: dict-del-while-iterate
Description: Deleting $DICT[$KEY] while iterating over $DICT.items() or $DICT.keys() mutates the underlying mapping that the view object is walking, which raises RuntimeError: dictionary changed size during iteration on the very next step. Snapshot the keys first with for $KEY in list($DICT): ... or build the kept entries into a new dict via a comprehension instead of deleting in place.
Disabled Cert Validation
Rule ID: disabled-cert-validation
Description: The cert_reqs argument supplied to urllib3.PoolManager/HTTPSConnectionPool/ssl.wrap_socket is set to CERT_NONE or CERT_OPTIONAL, which turns off TLS server-certificate verification and makes the connection vulnerable to anyone able to MITM the path. Use ssl.CERT_REQUIRED (the default) together with a trusted CA bundle so the peer is authenticated on every handshake.
CWE: CWE-295 · OWASP: A03:2017, A07:2021, A07:2025
Dynamic Urllib Use Detected
Rule ID: dynamic-urllib-use-detected
Description: A dynamic URL is being passed to urllib.urlopen/urllib.request.urlopen/urlretrieve (or a URLopener.open/retrieve). Because these helpers honour file://, ftp://, and other handlers, a tainted URL can be coerced into local file disclosure or SSRF. Switch to requests with an explicit scheme allowlist, or validate the URL's scheme/host before fetching.
CWE: CWE-939 · OWASP: A01:2017
Eval Detected
Rule ID: eval-detected
Description: Calling eval() on a non-literal expression compiles and runs the string as Python in the current scope, so any caller-controlled fragment becomes a remote-code-execution primitive. Replace eval with a parser tuned to the input — ast.literal_eval for safe Python literals, json.loads for JSON, or a hand-written grammar for structured DSLs — and never let untrusted data reach eval.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Exec Detected
Rule ID: exec-detected
Description: exec() is being called on a non-literal argument, which compiles and runs the supplied string as Python in the enclosing namespace. Any tainted byte that reaches this call is a remote-code-execution primitive. Refactor the logic so behavior is selected through ordinary control flow (a dict dispatch or getattr against an allowlist), and keep untrusted input far away from exec.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
File Object Redefined Before Close
Rule ID: file-object-redefined-before-close
Description: Reassigning $F from a second open(...) without calling $F.close() on the previous handle abandons that file descriptor; it stays open until the garbage collector eventually finalises the object, which on Windows blocks deletion and on long-running processes can exhaust the descriptor table. Close the first handle before reopening, or wrap each open in a with statement so it is released deterministically.
Formatted SQL Query
Rule ID: formatted-sql-query
Description: The SQL string passed to $DB.execute is built with % formatting, str.format, or an f-string, which concatenates raw values into the query text and exposes the call to SQL injection. Pass the SQL as a parameterized statement (cursor.execute("... WHERE x = ?", (value,))) so the driver, not Python's string formatter, handles quoting and types.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Hardcoded Password Default Argument
Rule ID: hardcoded-password-default-argument
Description: The password= parameter of $FUNC carries a hardcoded default string. Any caller that omits the argument will silently authenticate with that baked-in value, exposing the credential in source code and turning every code-search hit into a leak. Drop the default (require the caller to pass a value), or read the credential from an environment variable or a secrets manager at call time.
CWE: CWE-798 · OWASP: A07:2021, A07:2025
HTTP Not HTTPS Connection
Rule ID: http-not-https-connection
Description: urllib3.HTTPConnectionPool (or urllib3.connectionpool.HTTPConnectionPool) creates a pool that speaks plain HTTP, so every request reuses an unencrypted TCP connection and exposes traffic to interception on the wire. Swap the class for urllib3.HTTPSConnectionPool and configure it with a CA bundle so connections are encrypted and the server certificate is verified.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Httpsconnection Detected
Rule ID: httpsconnection-detected
Description: Constructing a http.client.HTTPSConnection (or its httplib/six.moves aliases) without an explicit context=ssl.create_default_context() relies on the interpreter's historical default — and on CPython before 3.4.3 that default did not verify the server certificate at all. Always pass a ssl.create_default_context() (or equivalent) so hostname and chain validation are enforced.
CWE: CWE-295 · OWASP: A03:2017, A07:2021, A07:2025
Identical Is Comparison
Rule ID: identical-is-comparison
Description: Comparing a value to itself with $S is $S always evaluates to True (except for the NaN corner case via ==) and is almost certainly a typo. Replace one side with the operand you actually meant to test against, or drop the redundant check entirely.
Insecure File Permissions
Rule ID: insecure-file-permissions
Description: The mode $BITS being passed to os.chmod/os.lchmod/os.fchmod grants group or world write/execute rights (or sets the S_IWGRP/S_IWOTH/S_IRWXG/S_IRWXO bits), which leaves the file readable or modifiable by users beyond the owner. Tighten the mode to something like 0o600 or 0o644 so only the owning user can modify the file.
CWE: CWE-276 · OWASP: A01:2021, A01:2025
Insecure Hash Algorithm MD5
Rule ID: insecure-hash-algorithm-md5
Description: hashlib.md5 is being invoked without usedforsecurity=False. MD5 is practically broken against collision attacks, so any signature, integrity tag, or password derivation built on it can be forged. Replace the call with hashlib.sha256 (or hashlib.sha3_256); if the hash is only used as a non-security checksum, set usedforsecurity=False to make the intent explicit.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm SHA1
Rule ID: insecure-hash-algorithm-sha1
Description: SHA-1 has demonstrated chosen-prefix collisions (SHAttered, Shambles), so hashlib.sha1 is no longer safe for digital signatures, certificate fingerprints, or other security-sensitive uses. Replace it with hashlib.sha256 or a SHA-3 variant; legitimate non-security checksums should switch to hashlib.sha256(..., usedforsecurity=False).
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Function
Rule ID: insecure-hash-function
Description: hashlib.new is being constructed with the algorithm name md4 or md5, both of which fail modern collision-resistance requirements and have public exploit chains (Flame malware, chosen-prefix attacks). Pass "sha256" (or a SHA-3 family identifier) to hashlib.new, or call the dedicated constructor directly such as hashlib.sha256().
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Urlopen
Rule ID: insecure-urlopen
Description: urllib.request.urlopen is being pointed at an http:// URL, so the HTTP exchange — headers, cookies, and body — leaves the process unencrypted and unauthenticated. Switch the scheme to https:// so TLS protects the request, and verify the response certificate via ssl.create_default_context().
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Urlopen FTP
Rule ID: insecure-urlopen-ftp
Description: urllib.request.urlopen is being asked to fetch an ftp:// URL, which sends credentials and file contents over the wire in cleartext. Because urllib cannot speak SFTP, swap this call for a real SFTP library such as paramiko.SFTPClient or pysftp, or front the transfer with FTPS via ftplib.FTP_TLS.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Uuid Version
Rule ID: insecure-uuid-version
Description: uuid.uuid1() derives identifiers from the host's MAC address and a timestamp, so its output is predictable and leaks hardware information. Attackers can guess neighbouring IDs (the "sandwich attack") whenever these values are exposed. Use uuid.uuid4() -- which is backed by os.urandom -- for any user-visible or security-relevant identifier.
CWE: CWE-330 · OWASP: A02:2021, A04:2025
Is Not Is Not
Rule ID: is-not-is-not
Description: Writing $S is (not ...) parses as the identity operator is applied to the boolean produced by not ..., so Python is actually asking whether $S is the very object True or False rather than performing the intended "is not" identity check. Drop the parentheses and use the compound operator $S is not ... so the comparison reads correctly.
List Modify While Iterate
Rule ID: list-modify-while-iterate
Description: Mutating $LIST with append, extend, insert, pop, or remove from inside for ... in $LIST shifts the indices that the iterator is walking, which causes elements to be skipped, visited twice, or — in the case of append/extend — produces an infinite loop. Iterate over a defensive copy (for $ELEMENT in list($LIST):) or collect the changes and apply them after the loop finishes.
Listen Eval
Rule ID: listen-eval
Description: logging.config.listen() accepts logging configs over a local socket and runs filter/() callable fields through eval, so any process able to write to that loopback port can execute Python in this interpreter. Pass a verify= callback that validates configurations against an HMAC or signature before they are applied — or do not expose the listener at all in production.
CWE: CWE-95 · OWASP: A03:2021, A05:2025
Mako Templates Detected
Rule ID: mako-templates-detected
Description: Instantiating mako.template.Template(...) produces a renderer that emits expression values verbatim, because Mako has no global autoescape — every interpolated variable must carry an explicit | h (HTML) or | u (URL) filter to be safe. Wrap each ${...} site with the appropriate filter, or move to a template engine such as Jinja2 with autoescape=True enabled by default.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Marshal Usage
Rule ID: marshal-usage
Description: marshal.dump/dumps/load/loads is being used outside CPython's internal .pyc use case; the format is documented as unsafe against malformed input and can crash the interpreter or be abused to load attacker-controlled code objects. Persist application data with json, tomllib, or a schema-driven binary format (Protocol Buffers, Avro) instead.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
MD5 Used As Password
Rule ID: md5-used-as-password
Description: An MD5 digest (hashlib.md5, hashlib.new("MD5", …), Crypto.Hash.MD5, or cryptography.hazmat.primitives.hashes.MD5) is feeding a parameter that looks like a password, yet MD5 is fast and unsalted — attackers can brute-force billions of guesses per second on commodity GPUs. Hash passwords with a deliberately slow KDF such as hashlib.scrypt, argon2-cffi, or bcrypt, using a per-user random salt.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Multiprocessing Recv
Rule ID: multiprocessing-recv
Description: Reading from a multiprocessing.connection.Connection or Client invokes pickle under the hood, so any peer that can write to this connection can trigger arbitrary code execution via crafted bytes. Authenticate the channel (for example with Connection.send_bytes plus an HMAC, or multiprocessing.connection.Client(authkey=...)) before calling recv(), or restrict the endpoint to a trusted Pipe() peer.
CWE: CWE-502 · OWASP: A08:2017, A08:2021, A08:2025
No Set Ciphers
Rule ID: no-set-ciphers
Description: Calling $CONTEXT.set_ciphers(...) overrides the curated cipher list that ssl.create_default_context() ships with and can silently re-enable weak suites such as RC4, 3DES, or anonymous DH. Unless you are enforcing a stricter modern profile, drop the call and rely on the default suite, or audit the cipher string carefully against an established standard like Mozilla's Modern profile.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Non Literal Import
Rule ID: non-literal-import
Description: importlib.import_module is being called with a non-literal module name, so anyone who can influence $NAME can pick which Python module is loaded and executed in this process — including attacker-supplied entries on sys.path. Resolve the requested feature through a hardcoded dispatch table or an explicit allowlist of acceptable module names before calling import_module.
CWE: CWE-706 · OWASP: A01:2021, A01:2025
Paramiko Exec Command
Rule ID: paramiko-exec-command
Description: A non-literal command is being passed to paramiko.SSHClient.exec_command, which runs the string on the remote server through a shell. If any portion of the argument originates from user input it enables remote command injection. Build the command from a hardcoded template and pass untrusted values as separate, validated parameters.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Paramiko Implicit Trust Host Key
Rule ID: paramiko-implicit-trust-host-key
Description: Calling set_missing_host_key_policy with paramiko.client.AutoAddPolicy or WarningPolicy causes the SSH client to silently accept any server host key, leaving the connection vulnerable to man-in-the-middle attacks. Pre-populate known_hosts and switch to paramiko.client.RejectPolicy or a custom subclass that performs real host key validation.
CWE: CWE-322 · OWASP: A02:2021, A04:2025
Pg8000 Sqli
Rule ID: pg8000-sqli
Description: SQL constructed via concatenation, % formatting, .format, or an f-string is reaching a pg8000 cursor (run, execute, executemany, or prepare). User-controlled fragments inside that string permit SQL injection. Rely on named parameters -- conn.run("SELECT :value FROM table", value=myvalue) -- or precompile statements with conn.prepare("SELECT (:v) FROM table").
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Psycopg Sqli
Rule ID: psycopg-sqli
Description: A psycopg2 cursor call (execute, executemany, mogrify) receives a dynamically built query string with non-literal interpolation, which exposes the application to SQL injection when those values come from user input. Use psycopg2.sql.SQL to compose identifiers safely and pass values via the pyformat binding style, e.g. cur.execute("SELECT * FROM table WHERE name = %s", (user_input,)).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Pytest Assert Match After Path Patch
Rule ID: pytest-assert_match-after-path-patch
Description: snapshot.assert_match from pytest-snapshot uses pathlib.Path internally to read and write snapshot files, so patching pathlib.Path (or one of its methods) earlier in the test redirects the snapshot's own filesystem calls and causes the assertion to load or write the wrong data. Restrict the patch to the production code under test (for example with mocker.patch.object on the importing module) so assert_match keeps a real Path.
Python Logger Credential Disclosure
Rule ID: python-logger-credential-disclosure
Description: The format string $FORMAT_STRING passed to a logger.debug/info/warning/error/... call references a credential keyword (api_key, secret, token, password, ...) alongside a %s placeholder, which will write the secret straight into the log stream. Logs are routinely shipped to third-party pipelines, so redact the value (***, secrets.compare_digest, structured masking) before logging or skip the field entirely.
CWE: CWE-532 · OWASP: A09:2021, A09:2025
Python Reverse Shell
Rule ID: python-reverse-shell
Description: Reverse-shell pattern detected: a socket.socket is connected to $IP:$PORT and then $BINPATH is launched via pty.spawn or subprocess.call, which is the classic payload an attacker uses to gain interactive shell access. Remove this code from production paths; if it belongs to a test or red-team script, isolate it from any deployable artifact.
CWE: CWE-553
Raise Not Base Exception
Rule ID: raise-not-base-exception
Description: raise in Python 3 only accepts a class (or instance) that derives from BaseException; raising a bare string, a number, or anything else immediately produces TypeError: exceptions must derive from BaseException and your real error never surfaces. Wrap the payload in a proper exception, e.g. raise ValueError("..."), so the runtime can propagate it correctly.
Regex Dos
Rule ID: regex_dos
Description: The pattern compiled via re.compile exhibits catastrophic backtracking behavior. When matched against attacker-crafted input, the regex engine can take exponential time and stall the process, causing a regular expression denial of service (ReDoS). Refactor the pattern to avoid nested quantifiers on overlapping character classes, or evaluate it with a linear-time engine such as google-re2.
CWE: CWE-1333 · OWASP: A06:2017
Request Session HTTP In With Context
Rule ID: request-session-http-in-with-context
Description: A URL beginning with http:// is being passed through a with requests.Session(...) as $SESSION block, so the request leaves the application in cleartext and any network attacker can read or tamper with credentials and payload. Change the scheme to https:// (and pin a trusted CA bundle via Session.verify) so the connection is authenticated and encrypted.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Request Session With HTTP
Rule ID: request-session-with-http
Description: An ad-hoc requests.Session(...).$W(...)/request(...) call is hitting an http:// URL, which means credentials, cookies, and response bodies all travel as plaintext. Migrate the URL to https:// and rely on Session.verify=True so TLS authenticates the host and encrypts the body in transit.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Request With HTTP
Rule ID: request-with-http
Description: requests.get/requests.post/requests.request is being called with a plaintext http:// URL. Any header, body, or auth token sent through this call crosses the network in clear and is trivially sniffable on shared infrastructure. Update the URL to https:// (keep verify=True) so the request enjoys TLS confidentiality and server authentication.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Return In Init
Rule ID: return-in-init
Description: Returning a non-None value from __init__ raises TypeError: __init__() should return None at construction time because Python reserves the return value of the initializer for itself. If you wanted to short-circuit the setup, use a bare return (or return None); if you wanted to expose a computed value, move it to a classmethod factory or set it on self.
Sha224 Hash
Rule ID: sha224-hash
Description: The 224-bit digest produced by hashlib.sha224 or hashlib.sha3_224 falls below the output length required by current NIST and many regulatory cryptography guidelines. Switch to hashlib.sha384, hashlib.sha512, or hashlib.sha3_384 so the hash retains sufficient strength for signatures and integrity checks.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
SSL Wrap Socket Is Deprecated
Rule ID: ssl-wrap-socket-is-deprecated
Description: The legacy ssl.wrap_socket() helper is deprecated and produces a TLS socket without SNI or hostname verification, which weakens transport security. Construct an ssl.SSLContext via ssl.create_default_context() and call context.wrap_socket(...) so certificate validation and modern defaults are applied.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
String Concat In List
Rule ID: string-concat-in-list
Description: Two adjacent string literals inside a list or set (e.g. ["foo" "bar"]) are silently merged by Python's implicit string concatenation, producing one element instead of two and quietly losing the comma you almost certainly forgot. Add the missing comma between the literals, or merge them with explicit + if a single combined element was intended.
String Is Comparison
Rule ID: string-is-comparison
Description: Using is against a string literal tests whether the two operands are the exact same object in memory, not whether their characters are equal. Small strings happen to be interned by CPython so this can appear to work in trivial cases, but it breaks on longer strings, different interpreters, and dynamically built values. Compare strings with == (or !=) so Python checks the contents.
Subprocess List Passed As String
Rule ID: subprocess-list-passed-as-string
Description: A list of arguments is being collapsed via " ".join(...) before being handed to subprocess.run, subprocess.Popen, subprocess.call, check_call, or check_output. Re-joining loses the boundary between arguments and reintroduces shell-style parsing risks for any token that contains whitespace or metacharacters. Pass the original list directly so each element remains a discrete argv entry.
CWE: CWE-78 · OWASP: A03:2021, A05:2025
Subprocess Shell True
Rule ID: subprocess-shell-true
Description: subprocess.$FUNC is invoked with shell=True, which routes the command through /bin/sh and inherits the current environment, expanding glob, pipe, and substitution metacharacters. If any portion of the command derives from external data, the shell becomes a command-injection sink. Set shell=False and pass the program with its arguments as a list.
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
System Wildcard Detected
Rule ID: system-wildcard-detected
Description: A tar, chmod, chown, or rsync invocation containing a * wildcard is being launched through os.system, os.popen*, or subprocess with shell=True. The shell expands the wildcard before the command runs, so a crafted filename such as -e sh script.sh can smuggle extra flags and achieve arbitrary command execution. List the target paths explicitly or terminate options with -- after disabling shell interpretation.
CWE: CWE-155 · OWASP: A01:2017
Telnetlib
Rule ID: telnetlib
Description: Any call into telnetlib opens a cleartext Telnet session, transmitting credentials and command output without encryption or authenticity guarantees. Replace the connection with an SSH client (for example paramiko.SSHClient with proper host key verification) or another TLS-protected transport.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Tempfile Insecure
Rule ID: tempfile-insecure
Description: tempfile.mktemp only reserves a candidate path and returns before any file is actually created, so an attacker (or a racing thread) can create that path first and have your subsequent open write into a file they control — a classic TOCTOU bug that the standard library explicitly deprecates. Switch to tempfile.NamedTemporaryFile, mkstemp, or TemporaryDirectory, which atomically create the file with safe permissions.
Tempfile Without Flush
Rule ID: tempfile-without-flush
Description: Reading $F.name from a tempfile.NamedTemporaryFile without first calling $F.flush() or $F.close() hands the path to another consumer while the data is still sitting in Python's write buffer, so the file on disk is empty or missing the latest writes. Flush (or close, when delete=False) the temp file before passing $F.name to a subprocess or library that opens it independently.
Test Is Missing Assert
Rule ID: test-is-missing-assert
Description: A bare $A == $B expression at statement level inside a test file computes a boolean and immediately discards it, so the test never actually fails when the equality does not hold. Prefix the comparison with assert (or the equivalent pytest/unittest helper) so the result is checked.
Uncaught Executor Exceptions
Rule ID: uncaught-executor-exceptions
Description: ThreadPoolExecutor.map(...) returns a lazy iterator and only re-raises a worker exception when its result is consumed, so discarding the return value swallows every failure raised inside the pool. Iterate over the result (for _ in $EXECUTOR.map(...): pass) or switch to submit plus Future.result() so worker exceptions actually surface.
Unverified SSL Context
Rule ID: unverified-ssl-context
Description: The code calls ssl._create_unverified_context() (directly or by reassigning ssl._create_default_https_context), which produces a context that skips certificate chain and hostname verification. TLS without these checks is trivially defeated by a man-in-the-middle. Use ssl.create_default_context() so the system trust store and hostname matching are enforced.
CWE: CWE-295 · OWASP: A03:2017, A07:2021, A07:2025
Use Defused XML
Rule ID: use-defused-xml
Description: Importing the stdlib xml package signals that XML parsing is happening through a parser that, by default, processes external entities and DTDs -- a well-known XXE and denial-of-service vector. Replace the import with the defusedxml package, whose drop-in replacements (defusedxml.ElementTree, defusedxml.sax, etc.) disable those features for untrusted input.
CWE: CWE-611 · OWASP: A04:2017, A05:2021, A02:2025
Use Defused XML Parse
Rule ID: use-defused-xml-parse
Description: Parsing a non-literal input with xml.etree.ElementTree.parse exposes the application to XML External Entity (XXE) and billion-laughs style denial of service, because the stdlib parser still resolves DTD entities by default. Swap the call for defusedxml.etree.ElementTree.parse, which disables entity expansion and external references.
CWE: CWE-611 · OWASP: A04:2017, A05:2021, A02:2025
Use Defused Xmlrpc
Rule ID: use-defused-xmlrpc
Description: Bringing in xmlrpc, xmlrpclib, or SimpleXMLRPCServer activates a transport whose default parser is susceptible to entity-expansion bombs and external-entity disclosure. Install defusedxml and call defusedxml.xmlrpc.monkey_patch() (or use the defusedxml.xmlrpc submodules directly) so XML-RPC traffic is parsed with hardened defaults.
CWE: CWE-776 · OWASP: A04:2017, A05:2021, A02:2025
Use Defusedcsv
Rule ID: use-defusedcsv
Description: csv.writer is being used to emit a CSV that may contain user-supplied cells. Spreadsheet clients interpret leading =, +, -, @, or tab characters as formulas, so a tainted value can trigger CSV injection -- data exfiltration or even local code execution -- once the recipient opens the file. Replace csv.writer with defusedcsv.writer, which keeps the same API while neutralising formula triggers.
CWE: CWE-1236 · OWASP: A01:2017, A03:2021, A05:2025
Use FTP TLS
Rule ID: use-ftp-tls
Description: Instantiating ftplib.FTP opens a plaintext FTP control channel, so credentials and file contents cross the network unencrypted and any on-path attacker can read or tamper with them. Use ftplib.FTP_TLS with context=ssl.create_default_context() and call prot_p() so both the command and data channels are encrypted.
CWE: CWE-319 · OWASP: A03:2017, A02:2021, A04:2025
Weak SSL Version
Rule ID: weak-ssl-version
Description: A protocol constant referencing SSLv2/v3 or TLS 1.0/1.1 (such as ssl.PROTOCOL_TLSv1 or pyOpenSSL.SSL.SSLv3_METHOD) is being used to configure TLS. These protocols are deprecated and harbor known cryptographic weaknesses. Negotiate ssl.PROTOCOL_TLS_CLIENT/PROTOCOL_TLS_SERVER with a minimum version of ssl.TLSVersion.TLSv1_2 (preferably 1.3).
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Writing To File In Read Mode
Rule ID: writing-to-file-in-read-mode
Description: Calling $FD.write(...) on a handle opened with mode "r" or "rb" raises io.UnsupportedOperation: not writable at runtime because the file was opened read-only. Open the file with a writable mode such as "w", "a", "r+", or their binary variants when you need to write to it.
Yield In Init
Rule ID: yield-in-init
Description: Including a yield anywhere in __init__ turns the initializer into a generator function, so calling MyClass(...) no longer constructs the object — it returns an unstarted generator and the instance is never initialized, which usually shows up later as TypeError: __init__() should return None. Move the lazy stream into a separate generator method on the class and keep __init__ purely for attribute setup.
Crypto Mode Without Authentication
Rule ID: crypto-mode-without-authentication
Description: AES.new(...) is configured with AES.MODE_CBC, AES.MODE_CTR, AES.MODE_CFB, or AES.MODE_OFB from PyCryptodome without an accompanying HMAC.new over the ciphertext, leaving the data without any integrity guarantee. An attacker who can tamper with ciphertext can run padding-oracle or bit-flipping attacks. Switch to AES.MODE_GCM (or another AEAD mode) so encryption and authentication are combined.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Blowfish
Rule ID: insecure-cipher-algorithm-blowfish
Description: Crypto.Cipher.Blowfish.new(...) (or its Cryptodome mirror) instantiates a 64-bit block cipher that is vulnerable to birthday attacks like Sweet32 after only a few gigabytes of ciphertext. Replace it with a modern AEAD construction such as AES-GCM (AES.new(key, AES.MODE_GCM)) or a ChaCha20/Salsa20 stream cipher.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm DES
Rule ID: insecure-cipher-algorithm-des
Description: Use of Crypto.Cipher.DES.new(...) or Crypto.Cipher.DES3.new(...) (and their Cryptodome equivalents) is flagged here. DES has a 56-bit effective key and is brute-forceable in hours, while NIST has deprecated 3DES because of its 64-bit block size. Migrate to AES.new(key, AES.MODE_GCM) for authenticated encryption.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Rc2
Rule ID: insecure-cipher-algorithm-rc2
Description: Crypto.Cipher.ARC2.new(...) (RC2) or its Cryptodome counterpart appears here. RC2 has known related-key and differential cryptanalysis weaknesses and a 64-bit block size that makes it unsuitable for any modern use case. Replace it with AES-GCM via AES.new(key, AES.MODE_GCM) or with ChaCha20-Poly1305.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Rc4
Rule ID: insecure-cipher-algorithm-rc4
Description: RC4 is instantiated here through Crypto.Cipher.ARC4.new(...) (or the Cryptodome namespace). The keystream produced by RC4 leaks statistical biases that have been weaponised against TLS and WPA, and IETF RFC 7465 prohibits its use entirely. Switch to ChaCha20-Poly1305 (ChaCha20_Poly1305.new) or AES-GCM.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Cipher Algorithm Xor
Rule ID: insecure-cipher-algorithm-xor
Description: Plain XOR encryption is constructed via Crypto.Cipher.XOR.new(...) or its Cryptodome mirror. A repeating-key XOR offers no cryptographic strength and is trivially broken with known-plaintext analysis. Use an authenticated symmetric cipher such as AES.new(key, AES.MODE_GCM) instead.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm Md2
Rule ID: insecure-hash-algorithm-md2
Description: Crypto.Hash.MD2.new(...) (or the Cryptodome.Hash.MD2.new variant) is being invoked. MD2 is a 128-bit digest from 1989 that has known preimage and collision attacks and is considered cryptographically broken. Use SHA256.new(), SHA3_256.new(), or BLAKE2b.new() from the same package instead.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm Md4
Rule ID: insecure-hash-algorithm-md4
Description: Hashing via Crypto.Hash.MD4.new(...) (or its Cryptodome.Hash.MD4.new mirror) is cryptographically unsafe: full collisions can be produced in fractions of a second on commodity hardware. Replace the call with a SHA-2, SHA-3, or BLAKE2 digest such as SHA256.new().
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm MD5
Rule ID: insecure-hash-algorithm-md5
Description: MD5 is being produced via Crypto.Hash.MD5.new(...) (or Cryptodome.Hash.MD5.new). MD5 has practical chosen-prefix collision attacks and is unsuitable for signatures, certificate fingerprints, or password hashing. For integrity digests choose SHA256.new() or BLAKE2b.new(); for password storage use a KDF such as Crypto.Protocol.KDF.scrypt or Argon2.
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insecure Hash Algorithm SHA1
Rule ID: insecure-hash-algorithm-sha1
Description: SHA-1 is produced here through Crypto.Hash.SHA.new(...) (or its Cryptodome.Hash.SHA.new counterpart). Since the 2017 SHAttered attack, SHA-1 collisions are demonstrably practical, making it unsafe for digital signatures or certificate hashing. Replace the call with SHA256.new() or SHA3_256.new().
CWE: CWE-327 · OWASP: A03:2017, A02:2021, A04:2025
Insufficient Dsa Key Size
Rule ID: insufficient-dsa-key-size
Description: Crypto.PublicKey.DSA.generate(...) (or its Cryptodome counterpart) is being called with bits below 2048, which leaves the resulting DSA key below the NIST SP 800-57 minimum strength. Pass bits=2048 or larger so the generated modulus matches contemporary security guidance.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Insufficient Rsa Key Size
Rule ID: insufficient-rsa-key-size
Description: An RSA modulus smaller than 3072 bits is being generated through Crypto.PublicKey.RSA.generate(...) (or its Cryptodome mirror). For keys with a lifetime beyond 2030 NIST recommends at least 3072 bits to maintain a 128-bit security level. Increase the bits argument so the generated key remains resistant to factoring attacks.
CWE: CWE-326 · OWASP: A03:2017, A02:2021, A04:2025
Mongo Client Bad Auth
Rule ID: mongo-client-bad-auth
Description: pymongo.MongoClient(..., authMechanism='MONGODB-CR') requests the legacy challenge-response handshake which was deprecated in MongoDB 3.6 and removed in MongoDB 4.0. The server will reject the handshake (or fall back to weaker behaviour on older deployments). Use the SCRAM-based mechanism authMechanism='SCRAM-SHA-256'.
CWE: CWE-477
Pyramid Authtkt Cookie Httponly Unsafe Default
Rule ID: pyramid-authtkt-cookie-httponly-unsafe-default
Description: AuthTktCookieHelper / AuthTktAuthenticationPolicy is instantiated without an explicit httponly argument, so the Pyramid auth-ticket cookie is issued with the framework default that leaves the value reachable from JavaScript. A successful XSS on any page can then exfiltrate the session ticket. Pass httponly=True when constructing the helper or policy.
CWE: CWE-1004 · OWASP: A05:2021, A02:2025
Pyramid Authtkt Cookie Httponly Unsafe Value
Rule ID: pyramid-authtkt-cookie-httponly-unsafe-value
Description: The Pyramid AuthTktCookieHelper (or AuthTktAuthenticationPolicy) is built with httponly=False explicitly, which exposes the auth-ticket cookie to document.cookie reads in the browser. Any XSS on the application becomes an instant account takeover. Flip the argument to httponly=True.
CWE: CWE-1004 · OWASP: A05:2021, A02:2025
Pyramid Authtkt Cookie Samesite
Rule ID: pyramid-authtkt-cookie-samesite
Description: The samesite argument supplied to AuthTktCookieHelper / AuthTktAuthenticationPolicy is something other than 'Lax', leaving the auth-ticket cookie attached to cross-site requests and reachable in CSRF or cross-origin navigation attacks. Set samesite='Lax' (or 'Strict' when the UX allows it) so the browser refuses to attach the ticket to third-party requests.
CWE: CWE-1275 · OWASP: A01:2021, A01:2025
Pyramid Authtkt Cookie Secure Unsafe Default
Rule ID: pyramid-authtkt-cookie-secure-unsafe-default
Description: No secure keyword is provided to AuthTktCookieHelper / AuthTktAuthenticationPolicy, so Pyramid emits the auth-ticket cookie over plain HTTP as well as HTTPS. A network attacker on the same Wi-Fi or any intermediary can capture the session ticket in transit. Construct the helper or policy with secure=True.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
Pyramid Authtkt Cookie Secure Unsafe Value
Rule ID: pyramid-authtkt-cookie-secure-unsafe-value
Description: AuthTktCookieHelper / AuthTktAuthenticationPolicy is configured with secure=False, which forces the auth-ticket cookie to also travel over unencrypted HTTP and lets a passive eavesdropper steal the session. Change the argument to secure=True so the browser only ships the ticket on TLS connections.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
Pyramid CSRF Check Disabled
Rule ID: pyramid-csrf-check-disabled
Description: This @view_config(...) decorator passes require_csrf=False, disabling Pyramid's per-view CSRF token validation for state-changing requests handled by the view. Attackers who can lure an authenticated user to a malicious page may trigger the action on the user's behalf. Remove the override or set require_csrf=True.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
Pyramid CSRF Check Disabled Globally
Rule ID: pyramid-csrf-check-disabled-globally
Description: Configurator.set_default_csrf_options(require_csrf=False) switches off the automatic CSRF-token check that Pyramid normally enforces for every unsafe HTTP method. Every view that relies on the default is now exposed to cross-site request forgery. Set require_csrf=True (or drop the override) to bring the project-wide protection back.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
Pyramid CSRF Origin Check Disabled
Rule ID: pyramid-csrf-origin-check-disabled
Description: Setting check_origin=False on @view_config(...) suppresses the Referer/Origin verification for this single view, so cross-site requests are accepted as long as they carry a CSRF token. Unless the CSRF storage policy is explicitly hardened, this widens the attack surface. Restore the default by passing check_origin=True or removing the keyword.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
Pyramid CSRF Origin Check Disabled Globally
Rule ID: pyramid-csrf-origin-check-disabled-globally
Description: A global call to Configurator.set_default_csrf_options(check_origin=False) turns off the Referer/Origin header validation that Pyramid normally runs for every unsafe HTTP method. With an insecure CSRF storage policy this leaves all views unprotected against cross-site request forgery. Re-enable it by setting check_origin=True or by removing the keyword entirely.
CWE: CWE-352 · OWASP: A01:2021, A01:2025
Pyramid Direct Use Of Response
Rule ID: pyramid-direct-use-of-response
Description: Attributes read from the Pyramid request object flow straight into pyramid.request.Response(...) or assignments such as request.response.body / .text / .unicode_body. The bytes are written to the client untouched, bypassing template-engine escaping and creating a reflected XSS sink. Route the output through a renderer like pyramid_jinja2 or pyramid.renderers so autoescaping can run.
CWE: CWE-79 · OWASP: A07:2017, A03:2021, A05:2025
Pyramid Set Cookie Httponly Unsafe Default
Rule ID: pyramid-set-cookie-httponly-unsafe-default
Description: A view calls response.set_cookie(...) without supplying the httponly keyword, so Pyramid emits the cookie with the HttpOnly attribute disabled and leaves it readable from client-side JavaScript. If the cookie carries session or identity data, any XSS finding becomes a session-hijack vector. Add httponly=True to the call.
CWE: CWE-1004 · OWASP: A05:2021, A02:2025
Pyramid Set Cookie Httponly Unsafe Value
Rule ID: pyramid-set-cookie-httponly-unsafe-value
Description: The view explicitly passes httponly=False to response.set_cookie(...), overriding Pyramid's safer behaviour and instructing the browser to make the cookie reachable through document.cookie. That gives any XSS payload direct access to the cookie value. Change the keyword to httponly=True.
CWE: CWE-1004 · OWASP: A05:2021, A02:2025
Pyramid Set Cookie Samesite Unsafe Default
Rule ID: pyramid-set-cookie-samesite-unsafe-default
Description: response.set_cookie(...) is invoked without a samesite keyword, so the Pyramid cookie is shipped without the SameSite attribute and browsers continue to attach it on cross-site navigations. That keeps the door open to CSRF and cross-origin tracking. Pass samesite='Lax' (or 'Strict') when issuing the cookie.
CWE: CWE-1275 · OWASP: A01:2021, A01:2025
Pyramid Set Cookie Samesite Unsafe Value
Rule ID: pyramid-set-cookie-samesite-unsafe-value
Description: The samesite keyword passed to response.set_cookie(...) is a value other than 'Lax', which weakens the cross-site protection that browsers apply to the cookie. Forged cross-origin requests can still attach it, enabling CSRF or leakage to embedding frames. Set the keyword to samesite='Lax' (or 'Strict').
CWE: CWE-1275 · OWASP: A01:2021, A01:2025
Pyramid Set Cookie Secure Unsafe Default
Rule ID: pyramid-set-cookie-secure-unsafe-default
Description: The view emits response.set_cookie(...) without the secure keyword, which means Pyramid omits the Secure attribute and browsers also send the cookie over plain-HTTP requests. Any network attacker on the path can capture it. Pass secure=True so the cookie is only transmitted on TLS connections.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
Pyramid Set Cookie Secure Unsafe Value
Rule ID: pyramid-set-cookie-secure-unsafe-value
Description: response.set_cookie(...) is called with secure=False, explicitly stripping the Secure flag from the issued cookie. The browser will then send it over HTTP as well as HTTPS, exposing the value to interception. Switch the keyword to secure=True.
CWE: CWE-614 · OWASP: A05:2021, A02:2025
Pyramid Sqlalchemy SQL Injection
Rule ID: pyramid-sqlalchemy-sql-injection
Description: User-controlled data taken from the Pyramid request object is interpolated into a raw SQL fragment via str.format(...) (or another non-binding formatter) and then passed to SQLAlchemy clauses such as filter(...), order_by(...), group_by(...), having(...) or distinct(...). This produces a classic SQL-injection sink. Bind parameters with text("...").bindparams(...) so the values are sent to the database as parameters, not as SQL.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Disabled Cert Validation
Rule ID: disabled-cert-validation
Description: A requests HTTP method (get, post, put, etc.) is being called with verify=False, so urllib3 skips TLS certificate chain validation. This lets any machine-in-the-middle present a forged certificate and intercept or rewrite the traffic. Remove the override (or set verify=True) and use verify=<ca_bundle> if a custom CA is required.
CWE: CWE-295 · OWASP: A03:2017, A07:2021, A07:2025
No Auth Over HTTP
Rule ID: no-auth-over-http
Description: A requests.* call is sending an auth= credential to an http:// URL, meaning the username, password, or bearer token is transmitted in cleartext where any network observer can capture it. Switch the URL scheme to https:// (or use a properly TLS-terminated endpoint) so the credentials are encrypted in transit.
CWE: CWE-523 · OWASP: A02:2017, A02:2021, A04:2025
String Concat
Rule ID: string-concat
Description: A sh.<bin>(...) invocation is being passed a string built from concatenation, str.format, or an f-string. If any of the interpolated values are attacker controlled they can break the argument boundary and execute arbitrary shell logic. Pass each token as a discrete positional argument (e.g. sh.git("commit", "-m", msg)).
CWE: CWE-78 · OWASP: A01:2017, A03:2021, A05:2025
Avoid Sqlalchemy Text
Rule ID: avoid-sqlalchemy-text
Description: A string assembled with concatenation, f-string interpolation, str.format, or % formatting is reaching sqlalchemy.text(...), which submits the statement to the database verbatim. Any tainted segment becomes raw SQL and yields injection. Build the statement with sqlalchemy.text("... :param ...").bindparams(param=value) or use the Core expression language (select, and_, or_).
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Bad Operator In Filter
Rule ID: bad-operator-in-filter
Description: Python-level boolean operators (is, is not, and, or, not, in) inside a Model.query.filter(...) clause are evaluated by the interpreter, not translated into SQL, so the predicate effectively collapses to a constant. Replace them with their SQLAlchemy equivalents: ==, !=, sqlalchemy.and_, sqlalchemy.or_, sqlalchemy.not_, and Column.in_(...).
Batch Import
Rule ID: batch-import
Description: db.session.add(...) is being invoked inside a loop, producing one autoflush per iteration and many small INSERTs. For bulk loading prefer session.add_all([...]) or session.bulk_save_objects([...]) so the rows can be flushed in a single round-trip.
Delete Where No Execute
Rule ID: delete-where-no-execute
Description: A SQLAlchemy Table.delete().where(...) statement was built but never handed to connection.execute(...), so no rows are actually removed at runtime. Either pass the statement to connection.execute(...), or rewrite the call as a Query-style session.query(Model).filter(...).delete() which executes immediately.
Len All Count
Rule ID: len-all-count
Description: Calling len(query.all()) materialises every matching row into Python objects just to count them, dragging the whole result set across the wire. Replace it with query.count() so the count is computed in the database with a SELECT COUNT(*).
Sqlalchemy Execute Raw Query
Rule ID: sqlalchemy-execute-raw-query
Description: A statement built with +, %, str.format, or an f-string is being passed to connection.execute(...). The interpolated values bypass parameter binding, so any attacker-controlled segment is appended directly to the SQL text and runs unescaped. Use a parameterised sqlalchemy.text("... :p ...").bindparams(p=value) form or move the predicate into the Core/ORM expression language.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Sqlalchemy SQL Injection
Rule ID: sqlalchemy-sql-injection
Description: A raw SQL fragment formatted with a non-bindparams interpolation (typically str.format or f-string) is being passed to query.filter(...), query.distinct(...), query.group_by(...), query.having(...), or query.order_by(...). Because the function parameter is concatenated into the SQL text, callers can inject arbitrary statements. Switch to text("... :name ...").bindparams(name=value) so the values are passed as bound parameters.
CWE: CWE-89 · OWASP: A01:2017, A03:2021, A05:2025
Twiml Injection
Rule ID: twiml-injection
Description: Tainted data flows into the twiml= argument of Client.calls.create(...) without passing through xml.sax.saxutils.escape or html.escape. Because TwiML is parsed as XML by Twilio, the unescaped value can introduce extra <Say>, <Dial>, or <Redirect> verbs and hijack the call flow. Sanitise the input or build the TwiML with the twilio.twiml.VoiceResponse builder instead of string interpolation.
CWE: CWE-91 · OWASP: A03:2021, A05:2025


