Auditing your python environment
Find vulnerabilities in your dependencies using the right tools
We have more and more hackers trying to steal information from users using malicious packages. Therefore, it is important to check frequently the dependencies of your project to know if vulnerabilities have been found. In this short tutorial, I will introduce you to two tools to help you audit your dependencies.
safety
The first one is safety. It is maintained by the pyup team which uses their custom safety database. It comes in two flavors:
- An open source version that we can freely use. It is updated once a month.
- A paid version that is updated frequently (more than once a month).
installation
To install the package, type the following command in your terminal:
$ pip install safety
or if you are using a more advanced python package installer like poetry:
$ poetry add -D safety
If you don’t want to install it directly in your project, and you are using GitHub, there is an official GitHub action you can use in your CI.
- uses: pyupio/safety@v1
# if you subscribed to a paid plan, you can insert your key like the following snippet
# it assumes that you have a secret key called SAFETY_API_KEY and define under
# Settings -> Secrets -> Actions in GitHub admin.
with:
api-key: ${{ secrets.SAFETY_API_KEY }}
usage
To test safety, we will install flask with version 0.5.
$ pip install flask==0.5
# or
$ poetry add flask==0.5
The usage is pretty straightforward. To scan vulnerabilities in your current python environment, you type:
$ safety check
You will see an output like the following:
...
REPORT Safety v2.1.1 is scanning for Vulnerabilities...
Scanning dependencies in your environment: -> /home/kevin/.cache/pypoetry/virtualenvs/orm-L9juRWWT-py3.8/lib/python3.8/site-packages Using non-commercial database
Found and scanned 64 packages
Timestamp 2022-08-17 22:52:00
3 vulnerabilities found
0 vulnerabilities ignored+=======================================================================================================================+
VULNERABILITIES FOUND
+=======================================================================================================================+-> Vulnerability found in flask version 0.5
Vulnerability ID: 38654
Affected spec: <0.12.3
ADVISORY: Flask 0.12.3 includes a fix for CVE-2019-1010083: Unexpected memory usage. The impact is denial of service.
The attack vector is crafted encoded JSON data.
NOTE: this may overlap CVE-2018-1000656.https://github.com/pallets/flask/pull/2695/commits/0e1e9a04aaf29ab78f721cfc79ac2a691f6e3929
CVE-2019-1010083
For more information, please visit https://pyup.io/vulnerabilities/CVE-2019-1010083/38654/
Scan was completed. 3 vulnerabilities were found.
...
+=======================================================================================================================+
REMEDIATIONS 3 vulnerabilities were found in 1 package. For detailed remediation & fix recommendations, upgrade to a commercial license.
...
If you want a more detailed output, you can run the previous command with the flag --full-report
:
$ safety check --full-report
Note: as you can see in the output, it is only when you subscribed to a paid plan that you have suggestions on how to fix this vulnerability issue. This is not the case with the second tool I will introduce. If you want to see the vulnerable packages without further information, you can run:
$ safety check --bare
flask
You can also scan packages through a requirements file.
$ safety check -r requirements.txt
Or scan a single package.
$ echo "package-to-check==0.1" | safety check --stdin
By default, the output is in text format, but you can change it in JSON if you want with the --output
option:
$ safety check --output json
To finish with safety, I just want to quote a part of its documentation if you want to use it in a commercial product.
By default it uses the open Python vulnerability database Safety DB, which is licensed for non-commercial use only.
For all commercial projects, Safely must be upgraded to use a PyUp API using the
--key
option.
If you don’t want to pay to audit your environment, just read below. 😁
pip-audit
The second tool I want to introduce to you is pip-audit. Folks maintain it at Trails of Bit with some Google support. It uses the Pypa Advisory Database via the PyPI JSON API as a source of vulnerability reports.
installation
You can install it using pip or poetry like the following:
$ pip install pip-audit
# or
$ poetry add -D pip-audit
If you are using GitHub, there is a GitHub action you can use:
jobs:
pip-audit:
steps:
- uses: trailofbits/gh-action-pip-audit@v0.0.4
# if you use a requirements file, you can add the following
with:
inputs: requirements.txt
More information about how to configure it can be found here.
You can also use a pre-commit hook, although I will not recommend it since it will always trigger a network request and reduce the developer experience.
- repo: https://github.com/trailofbits/pip-audit
rev: v2.4.3
hooks:
- id: pip-audit
args: [ "-r", "requirements.txt" ]ci:
# Leave pip-audit to only run locally and not in CI
# pre-commit.ci does not allow network calls
skip: [ pip-audit ]
usage
We will still consider having the flask package with version 0.5 in our environment. To run pip-audit in our current python environment, we can type:
$ pip-audit
We will have an output like the following:
Found 2 known vulnerabilities in 1 package
Name Version ID Fix Versions
----- ------- -------------- ------------
flask 0.5 PYSEC-2019-179 1.0
flask 0.5 PYSEC-2018-66 0.12.3
For a more detailed output use the --desc
flag:
$ pip-audit --desc
If you want to fix the issue, you can use the --fix
flag. The command will install the minimal version to fix the issue. Bear in mind that this workflow only works if you are using pip as your package dependency manager. If you are using another tool like pipenv or poetry, you probably want to see the fixed version without installing it with pip otherwise you will break your dependencies workflow. To achieve that, you must append the --dry-run
flag to the --fix
one.
$ pip-audit --fix --dry-run
INFO:pip_audit._cli:Dry run: would have upgraded Flask to 1.0
Found 2 known vulnerabilities in 1 package and fixed 0 vulnerabilities in 0 packages
Name Version ID Fix Versions
----- ------- -------------- ------------
flask 0.5 PYSEC-2019-179 1.0
flask 0.5 PYSEC-2018-66 0.12.3
Now we see that the minimum version to solve the flask issue is 1.0. If you are using poetry, you need to change the version in pyproject.toml file with something like ^1.0
and run the following command:
$ poetry update flask
Now we can sleep well 😁.
pip-audit can also scan a requirements file:
$ pip-audit -r ./requirements.txt
If you are not in a virtual environment and use the pyproject.toml file to list your dependencies, you can run:
# this assumes pyproject.toml is in the current directory
$ pip-audit .
By default, the output is in columns format, you can change it with the -f
option. There are many supported formats like markdown, JSON, etc... To know more about the different formats supported you can type pip-audit --help
in your terminal. Example of the use of the JSON format:
$ pip-audit -f json | python -m json.tool
which audit tool to use in your project?
The answer to this question depends on your needs and constraints. Here are some considerations to take into account when making your decision.
- safety has a company behind it that dedicates its time to finding vulnerabilities in python packages ahead of official CVE.
- To make the best use of safety i.e. have the best up-to-date vulnerability database, you should use a paid plan. You need to be sure you or your company has the budget for that.
- pip-audit is more open source than safety since the management of the database is transparent for all users.
- safety is not free to use for commercial projects.
- pip-audit provides a hint to fix a found vulnerability, safety doesn’t do that unless you subscribed to a paid plan.
- pip-audit may be integrated into pip in the future.
This is all for this tutorial, hope you enjoyed it. Take care of yourself and see you next time! 😁