Staying safe while using third-party packages in Python

Principal Software Engineer at FWD.hr. FOSS enthusiast. Learning every day :)
Search for a command to run...

Principal Software Engineer at FWD.hr. FOSS enthusiast. Learning every day :)
In this article we will create a simple exploit with Python. NOTE: Since the entire code snippet is not that long, no final code will be provided on GitLab this time. Foreword This article is STRICTLY for educational purposes. All the information in...

Is my code good enough? It works, but can it be more optimized? Can I shorten it a bit? Is it readable? Well - we all ask these kinds of questions at all stages of our developer careers. I will share some easy tips with you on how you can improve you...

If you ever used Python, most likely you used context managers without even knowing what they are and what they do or how they work - you just used them as per the documentation of some 3rd party package. In this article, I will try to explain what e...

Last time I showed you how to build a simple book store REST API. This time I want to take it a step further and show you how to build an (almost) production-ready API with real database storage instead of an in-memory mock that we used. Also, I will...

Whenever we are building something with Python, most of the time we rely on third-party packages so that we don't have to reinvent the wheel for some simple things like sending an HTTP request. By doing so we lose control of how safe we are with that decision. If you want to learn how to prevent security holes inside of your application or service, read below :)
Before I explain the simple way HOW to prevent security holes that you could have inside of your application, we should first understand WHY this is so important. Let me kick off this article with some interesting stats:
Here comes the scariest one:
In other words - 95% of the above could have been prevented. Unfortunately, human neglect is what leads to these numbers. Most of these could be prevented simply by making use of any sort of vulnerability scanner.
In my article, I will explain in detail how to utilize a simple scanner that will keep your dependencies safe :)
Let's assume you are building an app that will act as currency exchange software (GUI, API, ...). You will need to hook up to some sort of external API to get the exchange rate list that you will rely on to do the currency conventions. Now comes the tricky part - unless you will do EVERYTHING from scratch, you will probably make use of requests (3rd party) package.
This is where things are getting messy. For the requests to work, that 3rd party package ALSO uses other 3rd party packages and installs them inside of your app (docker image, venv, server ...). If you check the setup.py from the requests package you can see exactly which 3rd party packages are needed for requests to work:
requires = [
'charset_normalizer~=2.0.0; python_version >= "3"',
'chardet>=3.0.2,<5; python_version < "3"',
'idna>=2.5,<3; python_version < "3"',
'idna>=2.5,<4; python_version >= "3"',
'urllib3>=1.21.1,<1.27',
'certifi>=2017.4.17'
]
Not that bad, right? Well, the thing is that every one of these can ALSO have sub-packages they need to run themselves.
Ok, let's see all of this in action. Create a new project and create a virtual environment along with it. Activate the venv you just created:
source /path-to-your-venv/bin/activate
Once your venv is activated, create a file (outside of your venv in your project root) called requirements.in and put the following inside of it:
aiohttp
requests
For the fun of it, I also added the aiohttp package.
Now let's install pip-tools to compile the dependency tree:
pip install pip-tools
Build the dependency tree by executing the following:
pip-compile -r requirements.in
After it is done, you will notice a new file in your project root called requirements.txt. That's what we were looking for - the complete dependency tree. All of the requirements are there:
#
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile requirements.in
#
aiohttp==3.8.1
# via -r requirements.in
aiosignal==1.2.0
# via aiohttp
async-timeout==4.0.2
# via aiohttp
attrs==21.4.0
# via aiohttp
certifi==2021.10.8
# via requests
charset-normalizer==2.0.11
# via
# aiohttp
# requests
frozenlist==1.3.0
# via
# aiohttp
# aiosignal
idna==3.3
# via
# requests
# yarl
multidict==6.0.2
# via
# aiohttp
# yarl
requests==2.27.1
# via -r requirements.in
urllib3==1.26.8
# via requests
yarl==1.7.2
# via aiohttp
Do you see how many there are? And we only included two packages for our application. Imagine how many there are once you are finished creating your application :)
Now that we know WHY we should use a vulnerability scanner, it is time to see HOW to do it.
Safety to the rescue.
Safety (also a 3rd party package!) is a dependency security scanner that checks all of your application dependencies against the Python vulnerability database Safety DB. If there are any vulnerabilities found, safety will output them as a plain text or JSON format.
Safety can be used in many ways inside of your project and we will explore most of them.
Straight forward way of using safety is to just install it as you would normally do with any other 3rd party package:
pip install safety
After that we can start the scanner by using the following command:
safety check -r requirements.txt
If everything is ok, you will get the following output (trimmed down):
| REPORT
| checked 12 packages, using free DB (updated once a month)
| No known security vulnerabilities found.
Most of the time we will Dockerize our application for the sake of deployment/ease of use/ development/some other reason. Safety can fortunately also be used as a scanner of Docker images. Build your image as you would normally do and add a TAG to it. Afterward, run the command:
docker run -it --rm ${TAG} "/bin/bash -c \"pip install safety && safety check\"
If you don't want to install and run safety as a package, you can also make use of the Docker image of safety. That way you will hook up the contents of your requirements.txt and check them against the safety running inside of a Docker image:
cat requirements.txt | docker run -i --rm pyupio/safety safety check --stdin
If you are making use of CI services such as GitLab, you can also include safety as a part of your CI pipeline. Add the following as a step inside of any stage that you prefer in your .gitlab-ci.yml:
safety:
script:
- pip install safety
- safety check
Make sure that the image that you use for your pipelines has pip installed
You can also add safety as a part of your tox configuration:
[tox]
envlist = py39
[testenv]
deps =
safety
pytest
commands =
safety check
pytest
This way safety will run right before your tests execute.
If you are using pre-commit hooks, safety can also be added inside of your .pre-commit-config.yaml:
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.2.4
hooks:
- id: python-safety-dependencies-check
files: requirements.txt
While there are so many ways to keep your application safe regardless of the tech stack it was built in, this time I just wanted to show you why making use of a vulnerability scanner could be essential for your project. It is fairly simple to include one inside of your Python projects and there should be no excuse for not using one in the first place.
Stay safe and happy coding!
Like always, thanks for reading :)