The dangers of assert in Python
Dhruv Patel
2022年8月18日
0 分で読めます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, try/catch
statements, if/else
statements, 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 print
statements. However, unlike print
statements, assert statements can be unexpectedly risky to use!
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 assert
statement:
1assert expression
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!
Python’s built-in __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 safe_assert_example.py
:
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 p
, t
, and 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:
1python safe_assert_example.py
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 "time
":
1simpleInterest(3, "time", 8)
Now, re-run the program using this command:
1python safe_assert_example.py
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.
Keeping "`time=
" as the t
value, execute the simple interest program in optimized mode using the following command:
1python -O safe_assert_example.py
This returns:
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
simpleInterest
before the code is run.Using
if
statements 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 __debug__
to False
.
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 unsafe_assert_example.py
:
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'])
The function 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 unsafe_assert_example.py
to:
1authorize_admin_user(['user'])
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.