Auditing your python environment
Tags: python, security, audit
Published: 2022-08-18 14:37:08

We have more and more hackers trying to steal information from users using [malicious packages](https://arstechnica.com/information-technology/2022/08/10-malicious-python-packages-exposed-in-latest-repository-attack/). 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](https://pyup.io/) team which uses their custom safety
database. It comes in two flavors:

- An [open source](https://github.com/pyupio/safety-db) 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:

```shell
$ pip install safety
```

or if you are using a more advanced python package installer like [poetry](https://medium.com/analytics-vidhya/poetry-finally-an-all-in-one-tool-to-manage-python-packages-3c4d2538e828):

```shell
$ poetry add -D safety
```

If you don't want to install it directly in your project, and you are using [GitHub](https://github.com/), there is an official GitHub action you can use in your CI.

```yaml
 - 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](https://flask.palletsprojects.com/en/2.2.x/) with version **0.5**.

```shell
$ 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:

```shell
$ safety check
```

You will see an output like the following:

```shell
...
 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`:

```shell
$ 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 just want to see the vulnerable packages without further information, you can run:

```shell
$ safety check --bare
flask
```

You can also scan packages through a requirements file.

```shell
$ safety check -r requirements.txt
```

Or scan a single package.

```shell
$ 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:

```shell
$ safety check --output json
```

## pip-audit

The second tool I want to introduce to you is _pip-audit_. It is maintained by folks at [Trails of Bit](https://www.trailofbits.com/) with some Google support. It uses the [Pypa Advisory Database](https://github.com/pypa/advisory-database) via the [PyPI JSON API](https://warehouse.pypa.io/api-reference/json.html) as a source of vulnerability reports.

### installation

You can install it using pip or poetry like the following:

```shell
$ pip install pip-audit
# or
$ poetry add -D pip-audit
```

If you are using GitHub, there is a GitHub action you can use:

```yaml
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](https://github.com/marketplace/actions/gh-action-pip-audit).

You can also use a [pre-commit](https://pre-commit.com/) hook, although I will not recommend it since it will always trigger a network request and reduce the developer experience.

```yaml
- 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:

```shell
$ pip-audit
```

We will have an output like the following:

```shell
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:

```shell
$ pip-audit --desc
```

If you want to fix the issue, you can use the `--fix` flag. The command will install the minimal version fixing 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](https://pipenv.pypa.io/en/latest/) 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.

```shell
$ 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:

```shell
$ poetry update flask
```

Now we can sleep well 😁.

pip-audit can also scan a requirements file:

```shell
$ pip-audit -r ./requirements.txt
```

If you are not in a virtual environment and use the [pyproject.toml](https://peps.python.org/pep-0518/) file to list
your dependencies, you can run:

```shell
# 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:

```shell
$ 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 finding vulnerabilities in python packages ahead of
  official [CVE](https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures).
- 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.
- 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](https://discuss.python.org/t/towards-a-pip-audit-subcommand-for-vulnerability-analysis-management/17681)
  into pip in the future.

This is all for this tutorial, hope you enjoyed it. Take care of yourself and see you next time! 😁

---

If you like my article and want to continue learning with me, don’t hesitate to follow me here and subscribe to my newsletter on [substack](https://lewoudar.substack.com/) 😉