Skip to content

Support for offline operation (e.g. using local copy of PyPA advisory repo as vulnerability service) #698

Description

@riwoodward

Is your feature request related to a problem? Please describe.

Currently two vulnerability services are offered, pypi and osv, but these are both based on pip-audit retrieving information from the Internet (e.g. with URLs specified in the code). This is a problem when needing to operate on offline machines or with limited Internet access. As it stands, I don't think pip-audit can operate without an available Internet conncection?

Describe the solution you'd like

I would like an option to execute pip-audit using a locally available copy of the advisory database. For example, I could maintain a local mirror on my network for the PyPA advisory database repo (https://github.com/pypa/advisory-database), then when pip-audit needs to be run on any offline machine on my network, I could simply retrieve from the local mirror and pass the path for this to pip-audit for it to use as a vulnerability service. This would be particularly useful for offline / air-gapped CI systems.

I hacked together a quick implementation for this and it works well. I just modified the query function of pip_audit/_service/pypi.py to become as below, and passed the path to the local copy of PyPA advisory database repo as an env var (e.g. export PIPAUDITDB=~/advisory-database)

  def query(self, spec: Dependency) -> tuple[Dependency, list[VulnerabilityResult]]:
      """
      Queries PyPI for the given `Dependency` specification.

      See `VulnerabilityService.query`.
      """
      if spec.is_skipped():
          return spec, []
      spec = cast(ResolvedDependency, spec)

      # Path to local PyPA Advisory Database
      repo_path = os.environ['PIPAUDITDB']

      results: list[VulnerabilityResult] = []

      # Get list of YAML files representing the advisories for the dependency
      # TODO: error checking! Check path exists, is valid PyPA database etc
      vuln_yaml_paths = glob(f'{repo_path}/{spec.canonical_name}/*')

      for vuln_yaml_path in vuln_yaml_paths:
          with open(vuln_yaml_path,'r') as f:
              data = yaml.safe_load(f)

          introduced_version = Version(data['affected'][0]['ranges'][0]['events'][0]['introduced'])
          # TODO: how to handle case when no fixed version? i.e. an active vuln?
          fixed_version = Version(data['affected'][0]['ranges'][0]['events'][1]['fixed'])
          fix_versions = [fixed_version]

          if spec.version >= introduced_version and spec.version < fixed_version:
              id = data['id']
              description = data['details']
              fix_versions = fix_versions
              aliases = data['aliases']
              published = data['published']
              # Normalize description into a single line
              description = description.replace("\n", " ")

              results.append(
                  VulnerabilityResult(
                      id=id,
                      description=description,
                      fix_versions=fix_versions,
                      aliases=set(aliases),
                      published=published,
                  )
              )

      return spec, results

I guess to make this an officially supported feature, the path to the PyPA repo could be specified using the -s SERVICE, --vulnerability-service SERVICE arg? Or make another option beyond osv and pypi, e.g. pypa-repo with another arg for the path to said repo?

I note that offline indexes are already supported using the --index-url arg, so this could be complementary?

If interested, I could put together a PR (i.e. taking the above approach and adding error handling, proper use of args, tidy up etc)? I wanted to see what you thought of the method / proposed approach first though.

Describe alternatives you've considered

Running a local PyPI mirror including the JSON advisory info could work but that would be considerably more effort and resource usage to achieve the same goal.

Additional context

Really great tool otherwise - thanks!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    component:vuln-sourcesComponents that provide sources of vulnerability informationenhancementNew feature or requestupstreamItems that require upstream work or coordination

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions