Staying safe while using third-party packages in Python

Featured on Hashnode
Staying safe while using third-party packages in Python

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 :)

Introduction

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:

  • Cybercrime is up ~600% due to the COVID-19 pandemic
  • In 2020, the average time to identify a breach was 207 days
  • 43% of cyberattacks target small businesses
  • $3.86 million is the global average cost of a data breach

Here comes the scariest one:

  • 95% of cybersecurity breaches are a result of human error

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 :)

1. Dependency problem

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.

1.1 Building the complete dependency tree

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 :)

2. Safety

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.

2.1 Safety as a package

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.

2.2 Safety inside of Docker image

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\"

2.3 Safety as a Docker image

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

2.4 Safety inside of CI service

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

2.5 Safety inside of tox

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.

2.6 Safety as a pre-commit hook

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

3. Conclusion

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 :)