The dangers of assert in Python
August 18, 20220 mins read
There are many ways to find bugs in Python code: the built-in debugger (pdb), a healthy amount of unit tests, a debugger in an IDE like Pycharm or Visual Studio,
assert statements, or the tried and true practice of covering every inch of your codebase in
print() statements like it’s going out of style.
Assert statements can help us catch bugs quickly and are far less intrusive than copious amounts of
This article explores how to use asserts safely and what causes them to be unsafe. By the end of this article, you’ll know how to use
assert most optimally without inadvertently opening yourself up to security issues.
Using Python assert statements
Using assert is easy! Assert statements are simple statements, which means they can fit in one line of Python code.
Here’s a simple example of an
In the statement above, you’re asserting that the expression evaluates to
True, similar to a boolean check.
If the expression is
False, an exception will be thrown. For example:
1>>> assert 'hi' == 'there' 2Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4AssertionError
To make it easier to sift through a stack trace, you can specify an assertion message that will be included as part of the
AssertionError. This can be useful in complex debugging scenarios.
1>>> assert 'hi' == 'there', 'Values do not match!' 2Traceback (most recent call last): 3 File "<stdin>", line 1, in <module> 4AssertionError: Values do not match!
__debug__ variable must be set to
True when using assert statements. If it’s set to
False, assert statements won’t execute during runtime. This is a serious concern because running Python in optimized mode (commonly done in production environments by setting the PYTHONOPTIMIZE command line flag,
-O) sets the
__debug__ variable to
False, thereby disabling assert statements, which may inadvertently introduce vulnerabilities into your project. We’ll explore this in more detail below.
When used correctly,
assert is a potent tool for debugging. However, the more complex the usage, the more room there is to introduce security vulnerabilities during development.
TL;DR — Assert statements are incredibly useful for debugging Python applications but can’t be used for normal error handling or control flow because they’ll either:
Halt the application by raising an exception, which can cause production outages.
Cause unintended side effects (potentially including vulnerabilities) in your application when debug mode is disabled and assert the Python runtime silently skips statements.
Using assert safely
Let’s explore how to use assertion statements to debug a program by examining a sample application that calculates simple interest.
First, install a stable Python version. Next, open a folder and create a Python file named
safe_assert_example.py. You can use any supported code editor.
Add the following code to
1def simpleInterest(p, t, r): 2print('The principal is', p) 3assert isinstance(p, int) and p > 0, "Principal must be a positive integer" 4print('The time period is', t) 5assert isinstance(t, int) and t > 0, "Time must be a positive integer" 6print('The rate of interest is', r) 7assert isinstance(r, int) and r > 0, "Rate of interest must be a positive integer" 8simpleInterest = (p * t * r) / 100 9print('The Simple Interest is', simpleInterest) 10return simpleInterest 11 12simpleInterest(3, 5, 8)
This program defines the
simpleInterest function and calculates simple interest using principal, time, and rate.
To correctly calculate interest, you must input positive integers for principal, time, and rate. This example uses assert statements to check whether the
r values are positive integers. The program returns an informative error message when a value isn’t a positive integer.
Time to try it out! Run the program using the following command:
You should get the output shown below, with a simple interest of 1.2:
1PS E:\Sample\Extra\Assert Demo> python safe_assert_example.py 2The principal is 3 3The time period is 5 4The rate of interest is 8 5The Simple Interest is 1.2
As you can see, the program runs smoothly and doesn’t return an error message.
Now, see what happens when you try to run this program with invalid input. Try changing the
t value from
5 to the string "
1simpleInterest(3, "time", 8)
Now, re-run the program using this command:
This time, you should see the following error:
1PS E: \Sample\Extra\Assert Demo» python safe assert example.py 2The principal is 3 3The time period is time 4Traceback (most recent call last): 5 File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8) 6 File "E: \Sample\Extra\Assert Demo\safe assert example.py", line 5, in simpleInterest assert isinstance(t, int) and t > 0, "Time must be a positive integer" 7AssertionError: Time must be a positive integer
This output includes an
AssertionError. This exception highlights that the assert statement works as intended, as it successfully caught the invalid function argument.
In this example, assertion helped us debug our program and stopped the program’s execution as soon as the assertion failed.
Recall that running the program in optimized mode (by setting the
PYTHONOPTIMIZE command line flag,
-O) silently disables assert statements. Now, see how this behavior impacts the application.
=" as the
t value, execute the simple interest program in optimized mode using the following command:
1python -O safe_assert_example.py
1PS E:\Sample\Extra\Assert Demo› python -0 safe assert example.py 2The principal is 3 3The time period is time 4The rate of interest is 8 5Traceback (most recent call last): 6 File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 12, in <module> simpleInterest(3, "time", 8) 7 File "E:\Sample\Extra\Assert Demo\safe assert example.py", line 8, in simpleInterest simpleInterest = (p * t * r)/100 8TypeError: unsupported operand type(s) for /: 'str' and 'int'
Notice that even though the program has an invalid function argument, the output above doesn’t include the expected
AssertionError. Instead, Python outputs a
TypeError, as you’ve attempted to multiply integer and string type variables. This happens because the assert statements didn’t run, and suggests we should consider alternatives such as:
Adding type hints to help catch incorrect calls to
simpleInterestbefore the code is run.
ifstatements instead of assertions to check types at runtime.
Using assert unsafely
Assert should only be used for testing and debugging — not in production environments. Because assert statements only run when the
__debug__ variable is set to
True, a Python interpreter can disable them.
As demonstrated in the example above, running Python in optimized mode using
-O stops assert statements from working, as this sets
Because of this, it isn’t safe to use assert statements for anything that needs to run in a production environment. For example, you should never use asserts to validate tokens or user input. These statements won’t run in production, so any tokens or user inputs won’t be validated, potentially introducing vulnerabilities into your application.
To highlight the severity of using assert for validation in a production environment, consider a user authorization function that takes an array of users and then allows them to move further based on their access level. The program first uses assert to verify if the list or array isn’t null or not of a list instance using assert. Then, based on the role, it would allow the admin complete access to the application. If an admin role is found in the users list, then no assertion error would be prompted. But if we see no admin role in the list, it would throw the error.
Next, paste this code into a file named
1def authorize_admin_user(user_roles): 2 assert isinstance(user_roles,list) and user_roles != , "No user roles found" 3 4 assert 'admin' in user_roles, "No admin role found." 5 print("You have full access to the application.") 6 7authorize_admin_user(['admin','user'])
authorize_admin_user checks the user’s roles. If they’re an admin, it will show us that we have full access to the application. Typically, the user’s roles are loaded from the database; if admin is one of them, the user is granted full access to the app. Otherwise, the assert will fail and throw an exception.
The last line of the file calls the
authorize_admin_user function, passing in a list containing two roles: admin and user.
1Run python unsafe_assert_example.py: 2 3$ python unsafe_assert_example.py 4You have full access to the application
As expected, the user is granted full access to the application.
Let’s call the user role function without an admin role in the list. Change the last line of code in
Then, run the program and observe the output:
1$ python unsafe_assert_example.py 2Traceback (most recent call last): 3 File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 6, in <module> 4 authorize_admin_user(['user']) 5 File "C:\Projects\ContentLabEditing\python\unsafe_assert_example.py", line 3, in authorize_admin_user 6 assert 'admin' in user_roles, "No admin role found." 7AssertionError: No admin role found.
As expected, the function throws an
AssertionError because admin wasn’t in the list of roles.
Now, run the same program with optimized output:
1python -O unsafe_assert_example.py
The output will match the image below. Even though there was no
admin role in the list, the admin authorization succeeded because the assert did not run.
1PS E:\Sample\Extra\Assert Demo> python -0 unsafe_assert_example.py 2You have full access to the application 3You have full access to the application 4PS E:\Sample\Extra\Assert Demo>
Furthermore, changing the function call with a list of integers in the same optimized output execution would still showcase how assert should only be used for debugging purposes and not for validation.
Change the function call to
authorize_admin_user([1,2]) and run it using the same command
python -O unsafe_assert_example.py.
The output of the program remains the same.
In our simple interest calculator example, the consequences were minor when our assert statements did not run. Though we didn’t receive our desired error message, we could use Python’s error message to understand what went wrong. More importantly, we didn’t introduce any vulnerabilities because we were using assert statements for safe debugging purposes.
In this example, we used assert unsafely and made our Python application vulnerable by granting administrator access to a non-admin user.
If you’re interested in testing these examples further, you can find all the code from this article in this repository.
Do's and don't's with assert
Assert helps developers debug Python code easily while writing fewer lines of code. However, it can inadvertently introduce security issues into your applications if not used safely.
Disabling asserts in a production environment can be devastating. This practice can introduce various backdoors and breakpoints in the application that bad actors can take advantage of.
An assert’s primary function should be to isolate a bug’s root cause and should never be used for standard error handling. While asserts can be beneficial, it’s essential to use them for debugging and tests, and not to control the flow of the logic for the program or application and use them only in safe situations. If you’re looking for an alternative to assert, you will find using unit tests, debuggers, conditionals, and validators to be better, safer options.
Now that you know how to use assert safely, read the Python best practices cheat sheet to learn more about keeping your applications safe in all your platforms and environments.
More Python development and security resources
You can also try our free online Python code checker tool to see how the Snyk code engine analyses your code for security and quality issues.
Secure your code as you develop
Snyk scans your code for quality and security issues and get fix advice right in your IDE.