├── .github ├── dependabot.yml └── workflows │ ├── python.yml │ └── release.yml ├── .gitignore ├── .isort.cfg ├── .mypy.ini ├── .readthedocs.yaml ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── docs ├── Makefile ├── changelog.rst ├── conf.py ├── configuration.rst ├── data-sources.rst ├── index.rst ├── install.rst ├── make.bat ├── requirements.txt ├── support.rst └── usage.rst ├── poetry.lock ├── pyproject.toml ├── requirements.lowest.txt ├── tests ├── __init__.py └── test_source_ossindex.py ├── tox.ini └── vexy ├── __init__.py ├── __main__.py ├── client.py ├── py.typed └── sources ├── __init__.py ├── base.py ├── ossindex.py ├── osv.py └── osvdb.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'github-actions' 6 | directory: '/' 7 | schedule: 8 | interval: 'daily' 9 | labels: [ 'dependencies' ] 10 | commit-message: 11 | prefix: 'chore' ## prefix maximum string length of 15 12 | include: 'scope' 13 | open-pull-requests-limit: 999 14 | - package-ecosystem: 'docker' 15 | directory: '/' 16 | schedule: 17 | interval: 'daily' 18 | allow: 19 | - dependency-type: 'all' 20 | versioning-strategy: 'auto' 21 | labels: [ 'dependencies' ] 22 | commit-message: 23 | prefix: 'chore' ## prefix maximum string length of 15 24 | include: 'scope' 25 | open-pull-requests-limit: 999 26 | - package-ecosystem: 'pip' 27 | directory: '/' 28 | schedule: 29 | interval: 'daily' 30 | allow: 31 | - dependency-type: 'all' 32 | versioning-strategy: 'auto' 33 | labels: [ 'dependencies' ] 34 | commit-message: 35 | prefix: 'chore' ## prefix maximum string length of 15 36 | include: 'scope' 37 | open-pull-requests-limit: 999 38 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | name: Python CI 21 | 22 | on: 23 | workflow_dispatch: 24 | pull_request: 25 | branches-ignore: ['dependabot/**'] 26 | push: 27 | tags: [ 'v*.*.*' ] # run again on release tags to have tools mark them 28 | branches: [ 'main' ] 29 | 30 | env: 31 | REPORTS_DIR: CI_reports 32 | PYTHON_VERSION_DEFAULT: "3.10" 33 | POETRY_VERSION: "1.1.12" 34 | 35 | jobs: 36 | coding-standards: 37 | name: Linting & Coding Standards 38 | runs-on: ubuntu-latest 39 | timeout-minutes: 10 40 | steps: 41 | - name: Checkout 42 | # see https://github.com/actions/checkout 43 | uses: actions/checkout@v3 44 | 45 | - name: Setup Python Environment 46 | # see https://github.com/actions/setup-python 47 | uses: actions/setup-python@v4 48 | with: 49 | python-version: ${{ env.PYTHON_VERSION_DEFAULT }} 50 | architecture: 'x64' 51 | 52 | - name: Install poetry 53 | # see https://github.com/marketplace/actions/setup-poetry 54 | uses: Gr1N/setup-poetry@v8 55 | with: 56 | poetry-version: ${{ env.POETRY_VERSION }} 57 | 58 | - name: Install dependencies 59 | run: poetry install 60 | 61 | - name: Run tox 62 | run: poetry run tox -e flake8 -s false 63 | 64 | static-code-analysis: 65 | name: Static Coding Analysis (py${{ matrix.python-version}} ${{ matrix.toxenv-factor }}) 66 | runs-on: ubuntu-latest 67 | timeout-minutes: 10 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | include: 72 | - # test with the locked dependencies 73 | python-version: '3.10' 74 | toxenv-factor: 'locked' 75 | - # test with the lowest dependencies 76 | python-version: '3.7' 77 | toxenv-factor: 'lowest' 78 | steps: 79 | - name: Checkout 80 | # see https://github.com/actions/checkout 81 | uses: actions/checkout@v3 82 | 83 | - name: Setup Python Environment 84 | # see https://github.com/actions/setup-python 85 | uses: actions/setup-python@v4 86 | with: 87 | python-version: ${{ env.PYTHON_VERSION_DEFAULT }} 88 | architecture: 'x64' 89 | 90 | - name: Install poetry 91 | # see https://github.com/marketplace/actions/setup-poetry 92 | uses: Gr1N/setup-poetry@v7 93 | with: 94 | poetry-version: ${{ env.POETRY_VERSION }} 95 | 96 | - name: Install dependencies 97 | run: poetry install 98 | 99 | - name: Run tox 100 | run: poetry run tox -e mypy-${{ matrix.toxenv-factor }} -s false 101 | 102 | build-and-test: 103 | name: Test (${{ matrix.os }} py${{ matrix.python-version }} ${{ matrix.toxenv-factor }}) 104 | runs-on: ${{ matrix.os }} 105 | timeout-minutes: 10 106 | env: 107 | REPORTS_ARTIFACT: tests-reports 108 | strategy: 109 | fail-fast: false 110 | matrix: 111 | os: [ubuntu-latest, windows-latest, macos-latest] 112 | python-version: 113 | - "3.10" # highest supported 114 | - "3.9" 115 | - "3.8" 116 | - "3.7" # lowest supported 117 | toxenv-factor: ['locked'] 118 | include: 119 | - # test with the lowest dependencies 120 | os: 'ubuntu-latest' 121 | python-version: '3.7' 122 | toxenv-factor: 'lowest' 123 | steps: 124 | - name: Checkout 125 | # see https://github.com/actions/checkout 126 | uses: actions/checkout@v3 127 | - name: Create reports directory 128 | run: mkdir ${{ env.REPORTS_DIR }} 129 | - name: Setup Python Environment 130 | # see https://github.com/actions/setup-python 131 | uses: actions/setup-python@v4 132 | with: 133 | python-version: ${{ matrix.python-version }} 134 | architecture: 'x64' 135 | - name: Install poetry 136 | # see https://github.com/marketplace/actions/setup-poetry 137 | uses: Gr1N/setup-poetry@v7 138 | with: 139 | poetry-version: ${{ env.POETRY_VERSION }} 140 | - name: Install dependencies 141 | run: poetry install 142 | - name: Ensure build successful 143 | run: poetry build 144 | - name: Run tox 145 | run: poetry run tox -e py-${{ matrix.toxenv-factor }} -s false 146 | - name: Generate coverage reports 147 | run: > 148 | poetry run coverage report && 149 | poetry run coverage xml -o ${{ env.REPORTS_DIR }}/coverage-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.toxenv-factor }}.xml && 150 | poetry run coverage html -d ${{ env.REPORTS_DIR }} 151 | - name: Artifact reports 152 | if: ${{ ! cancelled() }} 153 | # see https://github.com/actions/upload-artifact 154 | uses: actions/upload-artifact@v3 155 | with: 156 | name: ${{ env.REPORTS_ARTIFACT }} 157 | path: ${{ env.REPORTS_DIR }} 158 | if-no-files-found: error 159 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | name: Release 21 | 22 | on: 23 | push: 24 | branches: 25 | - main 26 | workflow_dispatch: 27 | 28 | env: 29 | PYTHON_VERSION_DEFAULT: "3.10" 30 | POETRY_VERSION: "1.1.12" 31 | 32 | jobs: 33 | release: 34 | # https://github.community/t/how-do-i-specify-job-dependency-running-in-another-workflow/16482 35 | # limit this to being run on regular commits, not the commits that semantic-release will create 36 | if: github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'chore(release):') 37 | runs-on: ubuntu-latest 38 | concurrency: release 39 | steps: 40 | - name: Checkout code 41 | # see https://github.com/actions/checkout 42 | uses: actions/checkout@v3 43 | with: 44 | fetch-depth: 0 45 | 46 | - name: Setup python 47 | # see https://github.com/actions/setup-python 48 | uses: actions/setup-python@v4 49 | with: 50 | python-version: ${{ env.PYTHON_VERSION_DEFAULT }} 51 | architecture: 'x64' 52 | 53 | - name: Install and configure Poetry 54 | # See https://github.com/marketplace/actions/install-poetry-action 55 | uses: snok/install-poetry@v1 56 | with: 57 | version: ${{ env.POETRY_VERSION }} 58 | virtualenvs-create: true 59 | virtualenvs-in-project: true 60 | installer-parallel: true 61 | 62 | - name: Install dependencies 63 | run: poetry install --no-root 64 | 65 | - name: View poetry version 66 | run: poetry --version 67 | 68 | - name: Python Semantic Release 69 | # see https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html 70 | # see https://github.com/relekang/python-semantic-release 71 | uses: python-semantic-release/python-semantic-release@v7.33.2 72 | with: 73 | github_token: ${{ secrets.GITHUB_TOKEN }} 74 | repository_username: __token__ 75 | repository_password: ${{ secrets.PYPI_TOKEN }} 76 | pypi_token: ${{ secrets.PYPI_TOKEN }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exlude python build & distribution directories 2 | build/ 3 | dist/ 4 | *.egg-info* 5 | 6 | # Exlude *.pyc 7 | *.pyc 8 | 9 | # Exclude test-related items 10 | .tox/* 11 | 12 | # Exclude coverage 13 | .coverage 14 | test-reports 15 | 16 | # Exclude Python Virtual Environment 17 | venv/ 18 | .venv/ 19 | 20 | # Exlude IDE related files 21 | .idea/* 22 | .vscode/* 23 | 24 | # Exclude generated docs 25 | docs/_build 26 | 27 | # vexy config files 28 | *.vexy.config -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | ## read the docs: https://pycqa.github.io/isort/docs/configuration/options.html 3 | ## keep in sync with flake8 config - in `tox.ini` file 4 | known_first_party = vexy 5 | skip_gitignore = true 6 | skip_glob = 7 | build/*,dist/*,__pycache__,.eggs,*.egg-info*, 8 | *_cache,*.cache, 9 | .git/*,.tox/*,.venv/*,venv/* 10 | _OLD/*,_TEST/*, 11 | docs/* 12 | combine_as_imports = true 13 | default_section = THIRDPARTY 14 | ensure_newline_before_comments = true 15 | include_trailing_comma = true 16 | line_length = 120 17 | multi_line_output = 3 -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | 3 | files = vexy/ 4 | 5 | show_error_codes = True 6 | pretty = True 7 | 8 | warn_unreachable = True 9 | allow_redefinition = False 10 | 11 | # ignore_missing_imports = False 12 | # follow_imports = normal 13 | # follow_imports_for_stubs = True 14 | 15 | ### Strict mode ### 16 | warn_unused_configs = True 17 | disallow_subclassing_any = True 18 | disallow_any_generics = True 19 | disallow_untyped_calls = True 20 | disallow_untyped_defs = True 21 | disallow_incomplete_defs = True 22 | check_untyped_defs = True 23 | disallow_untyped_decorators = True 24 | no_implicit_optional = True 25 | warn_redundant_casts = True 26 | warn_unused_ignores = False 27 | warn_return_any = True 28 | no_implicit_reexport = True 29 | 30 | [mypy-pytest.*] 31 | ignore_missing_imports = True 32 | 33 | [mypy-tests.*] 34 | disallow_untyped_decorators = False 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | # Read the Docs configuration file 21 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 22 | 23 | # Required 24 | version: 2 25 | 26 | # Set the version of Python and other tools you might need 27 | build: 28 | os: ubuntu-20.04 29 | tools: 30 | python: "3.9" 31 | # You can also specify other tool versions: 32 | # nodejs: "16" 33 | # rust: "1.55" 34 | # golang: "1.17" 35 | 36 | # Build documentation in the docs/ directory with Sphinx 37 | sphinx: 38 | configuration: docs/conf.py 39 | 40 | # Formats 41 | formats: all 42 | 43 | # Optionally declare the Python requirements required to build your docs 44 | python: 45 | install: 46 | - method: pip 47 | path: . 48 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## v0.3.1 (2023-03-03) 6 | ### Fix 7 | * Handle parsing of BOM more safely ([`39d6f78`](https://github.com/madpah/vexy/commit/39d6f78fd517d4cfe53fa07214f69947762d71a0)) 8 | * Update to latest contracts with `cyclonedx-python-lib` ([`fda01e0`](https://github.com/madpah/vexy/commit/fda01e047717dc1bf952f07cddd5ad2d551e9e35)) 9 | * Bump to latest rc of `cyclonedx-python-lib` ([`8e3d05a`](https://github.com/madpah/vexy/commit/8e3d05a8cc0d1d50d317f7f1eb5dbceb6fe093f1)) 10 | * Handle parsing of BOM more safely ([`a89862a`](https://github.com/madpah/vexy/commit/a89862a039ad918f278a57b64197fa9009fa28e0)) 11 | 12 | ## v0.3.0 (2022-08-02) 13 | ### Feature 14 | * Added OSV.dev as data source ([`402c669`](https://github.com/madpah/vexy/commit/402c669ab3a07a7ca485e860635504789107a0f5)) 15 | 16 | ## v0.2.0 (2022-07-14) 17 | ### Feature 18 | * Add `vexy` as a Tool to generated VEX documents ([`f2378a8`](https://github.com/madpah/vexy/commit/f2378a820b88a6ee10036d4f771b5dd0e11925cb)) 19 | * Add `vexy` as a Tool to generated VEX documents ([`70ea250`](https://github.com/madpah/vexy/commit/70ea250609ed8bf673637483691406d6b56f9dd8)) 20 | 21 | ### Fix 22 | * Disable mypy `warn_unused_ignores` to get mypy passing on all variants ([`e331e72`](https://github.com/madpah/vexy/commit/e331e72aac0002543066151841bbbeb661d5be97)) 23 | 24 | ## v0.1.7 (2022-07-13) 25 | ### Fix 26 | * Use a known working release pipeline ([`c51e613`](https://github.com/madpah/vexy/commit/c51e6132f5a653385486eda5efa54faece7719e7)) 27 | 28 | ## v0.1.6 (2022-07-13) 29 | ### Fix 30 | * Release CI pipeline syntax error ([`b5b8529`](https://github.com/madpah/vexy/commit/b5b852955810082009a7c308f91d4a1284aa6368)) 31 | 32 | ## v0.1.5 (2022-07-13) 33 | ### Fix 34 | * Release CI pipeline syntax error ([`c0c7846`](https://github.com/madpah/vexy/commit/c0c78461c2e288825214640300917edfe24cb04f)) 35 | 36 | ## v0.1.4 (2022-07-13) 37 | ### Fix 38 | * Remove date parsing from source BOM ([`3d1e0d9`](https://github.com/madpah/vexy/commit/3d1e0d94917df6b4b32da06900c846e771720689)) 39 | 40 | ## v0.1.3 (2022-07-13) 41 | ### Fix 42 | * Remove parsing of input BOM timestamp - we do not use it ([`8b40f70`](https://github.com/madpah/vexy/commit/8b40f70487f20c4e21f72ed329330226082a31f3)) 43 | 44 | ## v0.1.2 (2022-07-13) 45 | ### Fix 46 | * Pin ci to use python-semantic-release@v7.28.1 as newer breaks CI ([`d1a1fe6`](https://github.com/madpah/vexy/commit/d1a1fe6f221fc9f557828188613c0e329a19a881)) 47 | 48 | ## v0.1.1 (2022-07-13) 49 | ### Fix 50 | * Typing broke some use cases ([`5965816`](https://github.com/madpah/vexy/commit/59658165a2789b59d93a0e3844b35b5c5fe303dd)) 51 | * Typing broke some use cases ([`c766507`](https://github.com/madpah/vexy/commit/c766507bcc5a84f61b7371ba8dd1bc51526a0a77)) 52 | 53 | ## v0.1.0 (2022-07-13) 54 | ### Feature 55 | * First alpha release supporting OSS Index as the sole data source ([`650bf52`](https://github.com/madpah/vexy/commit/650bf521675524d7869ebc1b8d0ccc0d2175aab7)) -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # see https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 2 | 3 | * @madpah 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Pull requests are welcome, but please read this guidelines first. 4 | 5 | ## Setup 6 | 7 | This project uses [poetry]. Have it installed and setup first. 8 | 9 | To install dev-dependencies and tools: 10 | 11 | ```shell 12 | poetry install 13 | ``` 14 | 15 | ## Code style 16 | 17 | This project uses [PEP8] Style Guide for Python Code. 18 | This project loves sorted imports. 19 | Get it all applied via: 20 | 21 | ```shell 22 | poetry run isort . 23 | poetry run autopep8 --in-place -r . 24 | ``` 25 | 26 | ## Documentation 27 | 28 | This project uses [Sphinx] to generate documentation which is automatically published to [RTFD][link_rtfd]. 29 | 30 | Source for documentation is stored in the `docs` folder in [RST] format. 31 | 32 | You can generate the documentation locally by running: 33 | 34 | ```shell 35 | cd docs 36 | pip install -r requirements.txt 37 | make html 38 | ``` 39 | 40 | ## Testing 41 | 42 | ```shell 43 | poetry run tox 44 | ``` 45 | 46 | ## Sign your commits 47 | 48 | Please sign your commits, 49 | to show that you agree to publish your changes under the current terms and licenses of the project. 50 | 51 | ```shell 52 | git commit --signed-off ... 53 | ``` 54 | 55 | [poetry]: https://python-poetry.org 56 | [PEP8]: https://www.python.org/dev/peps/pep-0008/ 57 | [Sphinx]: https://www.sphinx-doc.org/ 58 | [link_rtfd]: https://vexy.readthedocs.io/ 59 | [RST]: https://en.wikipedia.org/wiki/ReStructuredText 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Vexy 2 | Copyright (c) Paul Horton 3 | 4 | This product includes software developed by the CycloneDX community (https://cyclonedx.org/). 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vexy - Generate VEX in CycloneDX 2 | 3 | [![shield_gh-workflow-test]][link_gh-workflow-test] 4 | [![shield_rtfd]][link_rtfd] 5 | [![shield_pypi-version]][link_pypi] 6 | [![shield_docker-version]][link_docker] 7 | [![shield_license]][license_file] 8 | [![shield_twitter-follow]][link_twitter] 9 | 10 | ---- 11 | 12 | This project provides a runnable Python-based application for generating VEX (Vulnerability Exploitability Exchange) in 13 | CycloneDX format. 14 | 15 | This tool is intended to be supplied a [CycloneDX](https://cyclonedx.org/) SBOM file and will produce a separate VEX 16 | which contains known vulnerabilities from a selection of publicly available data sources. 17 | 18 | [CycloneDX](https://cyclonedx.org/) is a lightweight BOM specification that is easily created, human-readable, and simple to parse. 19 | 20 | Read the full [documentation][link_rtfd] for more details. 21 | 22 | ## Why? 23 | 24 | A SBOM (Software Bill of Materials) is great for cataloging / knowing what components compose a software product. 25 | 26 | The same SBOM (in CycloneDX format) can also note _known_ vulnerabilities. What is _known_ is for a given point 27 | in time - and will change as new vulnerabilities are discovered or disclosed. 28 | 29 | CycloneDX allows for separate BOM documents to reference each other through their 30 | [BOM Link](https://cyclonedx.org/capabilities/bomlink/) capability. 31 | 32 | Wouldn't it be great if you could periodically generate a VEX based from your SBOM to keep things up to date, 33 | without having to generate a fresh SBOM entirely? 34 | 35 | That is where **vexy** comes in. 36 | 37 | ## Installation 38 | 39 | Install this from [PyPi.org][link_pypi] using your preferred Python package manager. 40 | 41 | Example using `pip`: 42 | 43 | ```shell 44 | pip install vexy 45 | ``` 46 | 47 | Example using `poetry`: 48 | 49 | ```shell 50 | poetry add vexy 51 | ``` 52 | 53 | ## Usage 54 | 55 | ## Basic usage 56 | 57 | ```text 58 | $ vexy --help 59 | 60 | usage: vexy [-h] -i FILE_PATH [--format {xml,json}] [--schema-version {1.4}] [-o FILE_PATH] [--force] [-X] 61 | 62 | Vexy VEX Generator 63 | 64 | options: 65 | -h, --help show this help message and exit 66 | -X Enable debug output 67 | 68 | Input CycloneDX BOM: 69 | Where Vexy shall obtain it's input 70 | 71 | -i FILE_PATH, --in-file FILE_PATH 72 | CycloneDX BOM to read input from. Use "-" to read from STDIN. 73 | 74 | VEX Output Configuration: 75 | Choose the output format and schema version 76 | 77 | --format {xml,json} The output format for your SBOM (default: xml) 78 | --schema-version {1.4} 79 | The CycloneDX schema version for your VEX (default: 1.4) 80 | -o FILE_PATH, --o FILE_PATH, --output FILE_PATH 81 | Output file path for your SBOM (set to '-' to output to STDOUT) 82 | --force If outputting to a file and the stated file already exists, it will be overwritten. 83 | 84 | ``` 85 | 86 | ### Advanced usage and details 87 | 88 | See the full [documentation][link_rtfd] for advanced usage and details on input formats, switches and options. 89 | 90 | ## Python Support 91 | 92 | We endeavour to support all functionality for all [current actively supported Python versions](https://www.python.org/downloads/). 93 | However, some features may not be possible/present in older Python versions due to their lack of support. 94 | 95 | ## Contributing 96 | 97 | Feel free to open issues, bugreports or pull requests. 98 | See the [CONTRIBUTING][contributing_file] file for details. 99 | 100 | ## Copyright & License 101 | 102 | Vexy is Copyright (c) Paul Horton. All Rights Reserved. 103 | Permission to modify and redistribute is granted under the terms of the Apache 2.0 license. 104 | See the [LICENSE][license_file] file for the full license. 105 | 106 | [license_file]: https://github.com/madpah/vexy/blob/master/LICENSE 107 | [contributing_file]: https://github.com/madpah/vexy/blob/master/CONTRIBUTING.md 108 | [link_rtfd]: https://vexy.readthedocs.io/ 109 | 110 | [shield_gh-workflow-test]: https://img.shields.io/github/actions/workflow/status/madpah/vexy/python.yml?branch=main "build" 111 | [shield_rtfd]: https://img.shields.io/readthedocs/vexy?logo=readthedocs&logoColor=white 112 | [shield_pypi-version]: https://img.shields.io/pypi/v/vexy?logo=Python&logoColor=white&label=PyPI "PyPI" 113 | [shield_docker-version]: https://img.shields.io/docker/v/madpah/vexy?logo=docker&logoColor=white&label=docker "docker" 114 | [shield_license]: https://img.shields.io/github/license/madpah/vexy?logo=open%20source%20initiative&logoColor=white "license" 115 | [shield_twitter-follow]: https://img.shields.io/badge/Twitter-follow-blue?logo=Twitter&logoColor=white "twitter follow" 116 | [link_gh-workflow-test]: https://img.shields.io/github/actions/workflow/status/madpah/vexy/python.yml?branch=main 117 | [link_pypi]: https://pypi.org/project/vexy/ 118 | [link_docker]: https://hub.docker.com/r/madpah/vexy 119 | [link_twitter]: https://twitter.com/madpah 120 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. # Copyright 2022-Present Sonatype Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .. mdinclude:: ../CHANGELOG.md -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | import pkg_resources 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'Vexy - Generate VEX in CDX' 24 | copyright = '2022-Present Paul Horton' 25 | author = 'Paul Horton' 26 | 27 | # The full version, including alpha/beta/rc tags 28 | release = pkg_resources.get_distribution("vexy").version 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | "sphinx.ext.autodoc", 37 | "sphinx.ext.viewcode", 38 | "autoapi.extension", 39 | "sphinx_rtd_theme", 40 | "m2r2" 41 | ] 42 | 43 | # Document Python Code 44 | autoapi_type = 'python' 45 | autoapi_dirs = ['../vexy'] 46 | 47 | source_suffix = ['.rst', '.md'] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # List of patterns, relative to source directory, that match files and 53 | # directories to ignore when looking for source files. 54 | # This pattern also affects html_static_path and html_extra_path. 55 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 56 | 57 | # -- Options for HTML output ------------------------------------------------- 58 | 59 | # The theme to use for HTML and HTML Help pages. See the documentation for 60 | # a list of builtin themes. 61 | # 62 | html_theme = 'sphinx_rtd_theme' 63 | 64 | # Add any paths that contain custom static files (such as style sheets) here, 65 | # relative to this directory. They are copied after the builtin static files, 66 | # so a file named "default.css" will overwrite the builtin "default.css". 67 | html_static_path = ['_static'] 68 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | Configuration 19 | ============ 20 | 21 | `Vexy`_ will query the data sources you configured to obtain current known vulnerability information 22 | that relates to the Open Source components included in your input SBOM. 23 | 24 | Data Sources are configured in a YAML formatted file which is supplied to `vexy`_ using the ``-c`` or ``--config`` 25 | flag at the command line. A configuration file **MUST** be supplied to run `vexy`_. 26 | 27 | Configuration File Format 28 | ------------ 29 | 30 | Currently, the configuration file is used only to describe which data sources you would like `vexy`_ to 31 | utilise and any configuration that datasource requires - e.g. authentication details. 32 | 33 | An example configuration file might look as follows: 34 | 35 | .. code-block:: yaml 36 | 37 | sources: 38 | ossindex: 39 | username: 40 | password: 41 | osv: 42 | 43 | For details of what data sources are available and their specific configuration - see :doc:`data-sources`. 44 | 45 | .. _Vexy: https://github.com/madpah/vexy 46 | -------------------------------------------------------------------------------- /docs/data-sources.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | .. _data_sources: 19 | 20 | Data Sources 21 | ============ 22 | 23 | OSS Index 24 | ------------ 25 | 26 | - See https://ossindex.sonatype.org 27 | - Supports authentication: ✓ 28 | - Requires authentication: ✗ 29 | 30 | Configuration 31 | ~~~~~~~~~~~~~ 32 | 33 | .. code-block:: 34 | 35 | sources: 36 | ossindex: 37 | username: 38 | password: 39 | 40 | OSV.dev 41 | ------------ 42 | 43 | - See https://osv.dev/ 44 | - Supports authentication: ✗ 45 | - Requires authentication: ✗ 46 | 47 | Configuration 48 | ~~~~~~~~~~~~~ 49 | 50 | .. code-block:: 51 | 52 | sources: 53 | osv: -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | Vexy Documentation 19 | ==================================================== 20 | 21 | Software Bill of Materials (SBOMs) are gaining traction and are a great way to codify what dependencies 22 | your software relies on from the Open Source ecosystems (and internal libraries too!). 23 | 24 | The SBOM for a given release of a given piece of software should be static in terms of the components that 25 | comprise that release. 26 | 27 | `CycloneDX`_, in this authors view - the best Bill of Materials format, also allows for `Vulnerability 28 | Exploitability Exchange`_ (or VEX) information to be included in your BOM. 29 | 30 | Known vulnerabilities change over time - we always know more about the security posture of Open Source components 31 | tomorrow than we did today. So how do we keep our BOMs updated with this information? 32 | 33 | `CycloneDX`_ also allows for BOMs to interlink for the above reason. The best way to manage this scenario is to 34 | generate a BOM that describes your software release, excluding VEX data, and then have a tool (perhaps `vexy`?) 35 | produce you a VEX document (in `CycloneDX`_ format) that links back to your SBOM. 36 | 37 | Did I confuse you? If so - read more about `Independent BOM and VEX here`_. 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | :caption: Contents: 42 | 43 | install 44 | usage 45 | configuration 46 | data-sources 47 | support 48 | changelog 49 | 50 | 51 | .. _CycloneDX: https://cyclonedx.org 52 | .. _Vulnerability Exploitability Exchange: https://cyclonedx.org/capabilities/#vulnerability-exploitability-exchange-vex 53 | .. _Independent BOM and VEX here: https://cyclonedx.org/capabilities/vex/ -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | Installation 19 | ============ 20 | 21 | Install from `pypi.org`_ as you would any other Python module using your preferred package manager: 22 | 23 | .. code-block:: bash 24 | 25 | pip install vexy 26 | 27 | .. _pypi.org: https://pypi.org/project/vexy/ 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | m2r2>=0.3.2 19 | Sphinx>=4.3.2 20 | sphinx-autoapi>=1.8.4 21 | sphinx-rtd-theme>=1.0.0 -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | Support 19 | ======= 20 | 21 | If you run into issues utilising this library, please raise a `GitHub Issue`_. When raising an issue please include as 22 | much detail as possible including: 23 | 24 | * Version of ``vexy`` you have installed 25 | * Input(s) 26 | * Expected Output(s) 27 | * Actual Output(s) 28 | 29 | Python Version Support 30 | ---------------------- 31 | 32 | We endeavour to support all functionality for all `current actively supported Python versions`_. 33 | However, some features may not be possible/present in older Python versions due to their lack of support - which are 34 | noted below. 35 | 36 | 37 | .. _GitHub Issue: https://github.com/sonatype-nexus-community/vexy/issues 38 | .. _current actively supported Python versions: https://www.python.org/downloads/ -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | .. # This file is part of Vexy 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # SPDX-License-Identifier: Apache-2.0 16 | # Copyright (c) Paul Horton. All Rights Reserved. 17 | 18 | Usage 19 | ============ 20 | 21 | ``vexy`` is designed to be run as a standalone application. 22 | 23 | Once installed, you can call the tool via the following methods: 24 | 25 | .. code-block:: bash 26 | 27 | $ python3 -m vexy 28 | $ vexy 29 | 30 | The full documentation can be issued by running with ``--help``: 31 | 32 | .. code-block:: 33 | 34 | $ vexy --help 35 | usage: vexy [-h] -c VEXY_CONFIG [-q] [-X] -i FILE_PATH [--format {xml,json}] [--schema-version {1.4}] [-o FILE_PATH] [--force] 36 | 37 | Vexy VEX Generator 38 | 39 | options: 40 | -h, --help show this help message and exit 41 | -c VEXY_CONFIG, --config VEXY_CONFIG 42 | Configuration file for Vexy defining data sources to use and their configuration. 43 | -q Quiet - no console output 44 | -X Enable debug output 45 | 46 | Input CycloneDX BOM: 47 | Where Vexy shall obtain its input 48 | 49 | -i FILE_PATH, --in-file FILE_PATH 50 | CycloneDX BOM to read input from. Use "-" to read from STDIN. 51 | 52 | VEX Output Configuration: 53 | Choose the output format and schema version 54 | 55 | --format {xml,json} The output format for your SBOM (default: xml) 56 | --schema-version {1.4} 57 | The CycloneDX schema version for your VEX (default: 1.4) 58 | -o FILE_PATH, --o FILE_PATH, --output FILE_PATH 59 | Output file path for your SBOM (set to '-' to output to STDOUT) 60 | --force If outputting to a file and the stated file already exists, it will be overwritten. -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "attrs" 3 | version = "22.2.0" 4 | description = "Classes Without Boilerplate" 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [package.extras] 10 | cov = ["attrs", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 11 | dev = ["attrs"] 12 | docs = ["furo", "sphinx", "myst-parser", "zope.interface", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] 13 | tests = ["attrs", "zope.interface"] 14 | tests-no-zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] 15 | tests_no_zope = ["hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist", "cloudpickle", "mypy (>=0.971,<0.990)", "pytest-mypy-plugins"] 16 | 17 | [[package]] 18 | name = "autopep8" 19 | version = "1.6.0" 20 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 21 | category = "dev" 22 | optional = false 23 | python-versions = "*" 24 | 25 | [package.dependencies] 26 | pycodestyle = ">=2.8.0" 27 | toml = "*" 28 | 29 | [[package]] 30 | name = "certifi" 31 | version = "2022.12.7" 32 | description = "Python package for providing Mozilla's CA Bundle." 33 | category = "main" 34 | optional = false 35 | python-versions = ">=3.6" 36 | 37 | [[package]] 38 | name = "charset-normalizer" 39 | version = "3.0.1" 40 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 41 | category = "main" 42 | optional = false 43 | python-versions = "*" 44 | 45 | [[package]] 46 | name = "colorama" 47 | version = "0.4.6" 48 | description = "Cross-platform colored terminal text." 49 | category = "dev" 50 | optional = false 51 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 52 | 53 | [[package]] 54 | name = "commonmark" 55 | version = "0.9.1" 56 | description = "Python parser for the CommonMark Markdown spec" 57 | category = "main" 58 | optional = false 59 | python-versions = "*" 60 | 61 | [package.extras] 62 | test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] 63 | 64 | [[package]] 65 | name = "coverage" 66 | version = "6.5.0" 67 | description = "Code coverage measurement for Python" 68 | category = "dev" 69 | optional = false 70 | python-versions = ">=3.7" 71 | 72 | [package.extras] 73 | toml = ["tomli"] 74 | 75 | [[package]] 76 | name = "cyclonedx-python-lib" 77 | version = "4.0.0rc1" 78 | description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files." 79 | category = "main" 80 | optional = false 81 | python-versions = ">=3.7,<4.0" 82 | 83 | [package.dependencies] 84 | importlib-metadata = {version = ">=3.4", markers = "python_version < \"3.8\""} 85 | packageurl-python = ">=0.9" 86 | py-serializable = ">=0.11.0,<0.12.0" 87 | sortedcontainers = ">=2.4.0,<3.0.0" 88 | toml = ">=0.10.0,<0.11.0" 89 | 90 | [[package]] 91 | name = "distlib" 92 | version = "0.3.6" 93 | description = "Distribution utilities" 94 | category = "dev" 95 | optional = false 96 | python-versions = "*" 97 | 98 | [[package]] 99 | name = "filelock" 100 | version = "3.9.0" 101 | description = "A platform independent file lock." 102 | category = "dev" 103 | optional = false 104 | python-versions = ">=3.7" 105 | 106 | [package.extras] 107 | docs = ["furo (>=2022.12.7)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] 108 | testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)", "pytest (>=7.2)"] 109 | 110 | [[package]] 111 | name = "flake8" 112 | version = "4.0.1" 113 | description = "the modular source code checker: pep8 pyflakes and co" 114 | category = "dev" 115 | optional = false 116 | python-versions = ">=3.6" 117 | 118 | [package.dependencies] 119 | importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} 120 | mccabe = ">=0.6.0,<0.7.0" 121 | pycodestyle = ">=2.8.0,<2.9.0" 122 | pyflakes = ">=2.4.0,<2.5.0" 123 | 124 | [[package]] 125 | name = "flake8-annotations" 126 | version = "2.9.1" 127 | description = "Flake8 Type Annotation Checks" 128 | category = "dev" 129 | optional = false 130 | python-versions = ">=3.7,<4.0" 131 | 132 | [package.dependencies] 133 | attrs = ">=21.4" 134 | flake8 = ">=3.7" 135 | typed-ast = {version = ">=1.4,<2.0", markers = "python_version < \"3.8\""} 136 | 137 | [[package]] 138 | name = "flake8-bugbear" 139 | version = "22.12.6" 140 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." 141 | category = "dev" 142 | optional = false 143 | python-versions = ">=3.7" 144 | 145 | [package.dependencies] 146 | attrs = ">=19.2.0" 147 | flake8 = ">=3.0.0" 148 | 149 | [package.extras] 150 | dev = ["tox", "coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] 151 | 152 | [[package]] 153 | name = "flake8-isort" 154 | version = "4.2.0" 155 | description = "flake8 plugin that integrates isort ." 156 | category = "dev" 157 | optional = false 158 | python-versions = "*" 159 | 160 | [package.dependencies] 161 | flake8 = ">=3.2.1,<6" 162 | isort = ">=4.3.5,<6" 163 | 164 | [package.extras] 165 | test = ["pytest-cov"] 166 | 167 | [[package]] 168 | name = "idna" 169 | version = "3.4" 170 | description = "Internationalized Domain Names in Applications (IDNA)" 171 | category = "main" 172 | optional = false 173 | python-versions = ">=3.5" 174 | 175 | [[package]] 176 | name = "importlib-metadata" 177 | version = "4.2.0" 178 | description = "Read metadata from Python packages" 179 | category = "main" 180 | optional = false 181 | python-versions = ">=3.6" 182 | 183 | [package.dependencies] 184 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 185 | zipp = ">=0.5" 186 | 187 | [package.extras] 188 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 189 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 190 | 191 | [[package]] 192 | name = "isort" 193 | version = "5.11.5" 194 | description = "A Python utility / library to sort Python imports." 195 | category = "dev" 196 | optional = false 197 | python-versions = ">=3.7.0" 198 | 199 | [package.extras] 200 | pipfile-deprecated-finder = ["pipreqs", "requirementslib", "pip-shims (>=0.5.2)"] 201 | requirements-deprecated-finder = ["pipreqs", "pip-api"] 202 | colors = ["colorama (>=0.4.3,<0.5.0)"] 203 | plugins = ["setuptools"] 204 | 205 | [[package]] 206 | name = "mccabe" 207 | version = "0.6.1" 208 | description = "McCabe checker, plugin for flake8" 209 | category = "dev" 210 | optional = false 211 | python-versions = "*" 212 | 213 | [[package]] 214 | name = "mypy" 215 | version = "0.961" 216 | description = "Optional static typing for Python" 217 | category = "dev" 218 | optional = false 219 | python-versions = ">=3.6" 220 | 221 | [package.dependencies] 222 | mypy-extensions = ">=0.4.3" 223 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 224 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 225 | typing-extensions = ">=3.10" 226 | 227 | [package.extras] 228 | dmypy = ["psutil (>=4.0)"] 229 | python2 = ["typed-ast (>=1.4.0,<2)"] 230 | reports = ["lxml"] 231 | 232 | [[package]] 233 | name = "mypy-extensions" 234 | version = "1.0.0" 235 | description = "Type system extensions for programs checked with the mypy type checker." 236 | category = "dev" 237 | optional = false 238 | python-versions = ">=3.5" 239 | 240 | [[package]] 241 | name = "ossindex-lib" 242 | version = "1.1.1" 243 | description = "A library for querying the OSS Index free catalogue of open source components to help developers identify vulnerabilities, understand risk, and keep their software safe." 244 | category = "main" 245 | optional = false 246 | python-versions = ">=3.6,<4.0" 247 | 248 | [package.dependencies] 249 | importlib-metadata = {version = ">=3.4", markers = "python_version < \"3.8\""} 250 | packageurl-python = ">=0.9.0,<0.10.0" 251 | PyYAML = ">=5.4.1,<7.0.0" 252 | requests = ">=2.20.0,<3.0.0" 253 | tinydb = ">=4.5.0,<5.0.0" 254 | types-PyYAML = ">=5.4.1,<6.0.0" 255 | types-requests = ">=2.25.1,<3.0.0" 256 | types-setuptools = ">=57.0.0" 257 | 258 | [[package]] 259 | name = "osv-lib" 260 | version = "0.2.1" 261 | description = "A library for querying OSV (https://osv.dev) distributed vulnerability database." 262 | category = "main" 263 | optional = false 264 | python-versions = ">=3.6,<4.0" 265 | 266 | [package.dependencies] 267 | importlib-metadata = {version = ">=3.4", markers = "python_version < \"3.8\""} 268 | packageurl-python = ">=0.9.0,<0.10.0" 269 | requests = ">=2.20.0,<3.0.0" 270 | types-requests = ">=2.25.1,<3.0.0" 271 | 272 | [[package]] 273 | name = "packageurl-python" 274 | version = "0.9.9" 275 | description = "A purl aka. Package URL parser and builder" 276 | category = "main" 277 | optional = false 278 | python-versions = ">=3.6" 279 | 280 | [package.extras] 281 | test = ["isort", "pytest"] 282 | 283 | [[package]] 284 | name = "packaging" 285 | version = "23.0" 286 | description = "Core utilities for Python packages" 287 | category = "dev" 288 | optional = false 289 | python-versions = ">=3.7" 290 | 291 | [[package]] 292 | name = "platformdirs" 293 | version = "2.6.2" 294 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 295 | category = "dev" 296 | optional = false 297 | python-versions = ">=3.7" 298 | 299 | [package.dependencies] 300 | typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} 301 | 302 | [package.extras] 303 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.5)", "sphinx (>=5.3)"] 304 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"] 305 | 306 | [[package]] 307 | name = "pluggy" 308 | version = "1.0.0" 309 | description = "plugin and hook calling mechanisms for python" 310 | category = "dev" 311 | optional = false 312 | python-versions = ">=3.6" 313 | 314 | [package.dependencies] 315 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 316 | 317 | [package.extras] 318 | dev = ["pre-commit", "tox"] 319 | testing = ["pytest", "pytest-benchmark"] 320 | 321 | [[package]] 322 | name = "py" 323 | version = "1.11.0" 324 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 325 | category = "dev" 326 | optional = false 327 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 328 | 329 | [[package]] 330 | name = "py-serializable" 331 | version = "0.11.0" 332 | description = "Library for serializing and deserializing Python Objects to and from JSON and XML." 333 | category = "main" 334 | optional = false 335 | python-versions = ">=3.7,<4.0" 336 | 337 | [[package]] 338 | name = "pycodestyle" 339 | version = "2.8.0" 340 | description = "Python style guide checker" 341 | category = "dev" 342 | optional = false 343 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 344 | 345 | [[package]] 346 | name = "pyflakes" 347 | version = "2.4.0" 348 | description = "passive checker of Python programs" 349 | category = "dev" 350 | optional = false 351 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 352 | 353 | [[package]] 354 | name = "pygments" 355 | version = "2.14.0" 356 | description = "Pygments is a syntax highlighting package written in Python." 357 | category = "main" 358 | optional = false 359 | python-versions = ">=3.6" 360 | 361 | [package.extras] 362 | plugins = ["importlib-metadata"] 363 | 364 | [[package]] 365 | name = "pyyaml" 366 | version = "6.0" 367 | description = "YAML parser and emitter for Python" 368 | category = "main" 369 | optional = false 370 | python-versions = ">=3.6" 371 | 372 | [[package]] 373 | name = "requests" 374 | version = "2.28.2" 375 | description = "Python HTTP for Humans." 376 | category = "main" 377 | optional = false 378 | python-versions = ">=3.7, <4" 379 | 380 | [package.dependencies] 381 | certifi = ">=2017.4.17" 382 | charset-normalizer = ">=2,<4" 383 | idna = ">=2.5,<4" 384 | urllib3 = ">=1.21.1,<1.27" 385 | 386 | [package.extras] 387 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 388 | use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] 389 | 390 | [[package]] 391 | name = "rich" 392 | version = "12.6.0" 393 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 394 | category = "main" 395 | optional = false 396 | python-versions = ">=3.6.3,<4.0.0" 397 | 398 | [package.dependencies] 399 | commonmark = ">=0.9.0,<0.10.0" 400 | pygments = ">=2.6.0,<3.0.0" 401 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 402 | 403 | [package.extras] 404 | jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] 405 | 406 | [[package]] 407 | name = "six" 408 | version = "1.16.0" 409 | description = "Python 2 and 3 compatibility utilities" 410 | category = "dev" 411 | optional = false 412 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 413 | 414 | [[package]] 415 | name = "sortedcontainers" 416 | version = "2.4.0" 417 | description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" 418 | category = "main" 419 | optional = false 420 | python-versions = "*" 421 | 422 | [[package]] 423 | name = "tinydb" 424 | version = "4.7.1" 425 | description = "TinyDB is a tiny, document oriented database optimized for your happiness :)" 426 | category = "main" 427 | optional = false 428 | python-versions = ">=3.7,<4.0" 429 | 430 | [package.dependencies] 431 | typing-extensions = {version = ">=3.10.0,<5.0.0", markers = "python_full_version <= \"3.7.0\""} 432 | 433 | [[package]] 434 | name = "toml" 435 | version = "0.10.2" 436 | description = "Python Library for Tom's Obvious, Minimal Language" 437 | category = "main" 438 | optional = false 439 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 440 | 441 | [[package]] 442 | name = "tomli" 443 | version = "2.0.1" 444 | description = "A lil' TOML parser" 445 | category = "dev" 446 | optional = false 447 | python-versions = ">=3.7" 448 | 449 | [[package]] 450 | name = "tox" 451 | version = "3.28.0" 452 | description = "tox is a generic virtualenv management and test command line tool" 453 | category = "dev" 454 | optional = false 455 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 456 | 457 | [package.dependencies] 458 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 459 | filelock = ">=3.0.0" 460 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 461 | packaging = ">=14" 462 | pluggy = ">=0.12.0" 463 | py = ">=1.4.17" 464 | six = ">=1.14.0" 465 | tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} 466 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 467 | 468 | [package.extras] 469 | docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] 470 | testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] 471 | 472 | [[package]] 473 | name = "typed-ast" 474 | version = "1.5.4" 475 | description = "a fork of Python 2 and 3 ast modules with type comment support" 476 | category = "dev" 477 | optional = false 478 | python-versions = ">=3.6" 479 | 480 | [[package]] 481 | name = "types-pyyaml" 482 | version = "5.4.12" 483 | description = "Typing stubs for PyYAML" 484 | category = "main" 485 | optional = false 486 | python-versions = "*" 487 | 488 | [[package]] 489 | name = "types-requests" 490 | version = "2.28.11.15" 491 | description = "Typing stubs for requests" 492 | category = "main" 493 | optional = false 494 | python-versions = "*" 495 | 496 | [package.dependencies] 497 | types-urllib3 = "<1.27" 498 | 499 | [[package]] 500 | name = "types-setuptools" 501 | version = "67.4.0.3" 502 | description = "Typing stubs for setuptools" 503 | category = "main" 504 | optional = false 505 | python-versions = "*" 506 | 507 | [[package]] 508 | name = "types-urllib3" 509 | version = "1.26.25.8" 510 | description = "Typing stubs for urllib3" 511 | category = "main" 512 | optional = false 513 | python-versions = "*" 514 | 515 | [[package]] 516 | name = "typing-extensions" 517 | version = "4.5.0" 518 | description = "Backported and Experimental Type Hints for Python 3.7+" 519 | category = "main" 520 | optional = false 521 | python-versions = ">=3.7" 522 | 523 | [[package]] 524 | name = "urllib3" 525 | version = "1.26.14" 526 | description = "HTTP library with thread-safe connection pooling, file post, and more." 527 | category = "main" 528 | optional = false 529 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 530 | 531 | [package.extras] 532 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 533 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] 534 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 535 | 536 | [[package]] 537 | name = "virtualenv" 538 | version = "20.16.2" 539 | description = "Virtual Python Environment builder" 540 | category = "dev" 541 | optional = false 542 | python-versions = ">=3.6" 543 | 544 | [package.dependencies] 545 | distlib = ">=0.3.1,<1" 546 | filelock = ">=3.2,<4" 547 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 548 | platformdirs = ">=2,<3" 549 | 550 | [package.extras] 551 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 552 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] 553 | 554 | [[package]] 555 | name = "zipp" 556 | version = "3.15.0" 557 | description = "Backport of pathlib-compatible object wrapper for zip files" 558 | category = "main" 559 | optional = false 560 | python-versions = ">=3.7" 561 | 562 | [package.extras] 563 | docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] 564 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] 565 | 566 | [metadata] 567 | lock-version = "1.1" 568 | python-versions = "^3.7" 569 | content-hash = "b4cb97cbc902b7f7d8cf359860fb17015a5a935029ff4698d4289cebbefa3f46" 570 | 571 | [metadata.files] 572 | attrs = [ 573 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 574 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 575 | ] 576 | autopep8 = [ 577 | {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, 578 | {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, 579 | ] 580 | certifi = [ 581 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 582 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 583 | ] 584 | charset-normalizer = [ 585 | {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, 586 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, 587 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, 588 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, 589 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, 590 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, 591 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, 592 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, 593 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, 594 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, 595 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, 596 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, 597 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, 598 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, 599 | {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, 600 | {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, 601 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, 602 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, 603 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, 604 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, 605 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, 606 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, 607 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, 608 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, 609 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, 610 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, 611 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, 612 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, 613 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, 614 | {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, 615 | {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, 616 | {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, 617 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, 618 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, 619 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, 620 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, 621 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, 622 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, 623 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, 624 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, 625 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, 626 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, 627 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, 628 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, 629 | {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, 630 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, 631 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, 632 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, 633 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, 634 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, 635 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, 636 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, 637 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, 638 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, 639 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, 640 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, 641 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, 642 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, 643 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, 644 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, 645 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, 646 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, 647 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, 648 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, 649 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, 650 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, 651 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, 652 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, 653 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, 654 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, 655 | {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, 656 | {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, 657 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, 658 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, 659 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, 660 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, 661 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, 662 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, 663 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, 664 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, 665 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, 666 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, 667 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, 668 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, 669 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, 670 | {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, 671 | {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, 672 | {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, 673 | ] 674 | colorama = [ 675 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 676 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 677 | ] 678 | commonmark = [ 679 | {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, 680 | {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, 681 | ] 682 | coverage = [ 683 | {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, 684 | {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, 685 | {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, 686 | {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, 687 | {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, 688 | {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, 689 | {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, 690 | {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, 691 | {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, 692 | {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, 693 | {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, 694 | {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, 695 | {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, 696 | {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, 697 | {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, 698 | {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, 699 | {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, 700 | {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, 701 | {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, 702 | {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, 703 | {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, 704 | {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, 705 | {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, 706 | {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, 707 | {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, 708 | {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, 709 | {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, 710 | {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, 711 | {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, 712 | {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, 713 | {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, 714 | {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, 715 | {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, 716 | {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, 717 | {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, 718 | {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, 719 | {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, 720 | {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, 721 | {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, 722 | {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, 723 | {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, 724 | {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, 725 | {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, 726 | {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, 727 | {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, 728 | {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, 729 | {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, 730 | {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, 731 | {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, 732 | {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, 733 | ] 734 | cyclonedx-python-lib = [ 735 | {file = "cyclonedx_python_lib-4.0.0rc1-py3-none-any.whl", hash = "sha256:ca36ffb3008217c010e2a0ee51afb1b9983e8c38cc0af2125272a65df217f3a6"}, 736 | {file = "cyclonedx_python_lib-4.0.0rc1.tar.gz", hash = "sha256:fe13a7d597c454fa6df85f4821160e43225b7aa93522b3f3a7ea8465eb469e2c"}, 737 | ] 738 | distlib = [ 739 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 740 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 741 | ] 742 | filelock = [ 743 | {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, 744 | {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, 745 | ] 746 | flake8 = [ 747 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 748 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 749 | ] 750 | flake8-annotations = [ 751 | {file = "flake8-annotations-2.9.1.tar.gz", hash = "sha256:11f09efb99ae63c8f9d6b492b75fe147fbc323179fddfe00b2e56eefeca42f57"}, 752 | {file = "flake8_annotations-2.9.1-py3-none-any.whl", hash = "sha256:a4385158a7a9fc8af1d8820a2f4c8d03387997006a83f5f8bfe5bc6085bdf88a"}, 753 | ] 754 | flake8-bugbear = [ 755 | {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, 756 | {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, 757 | ] 758 | flake8-isort = [ 759 | {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, 760 | {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, 761 | ] 762 | idna = [ 763 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 764 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 765 | ] 766 | importlib-metadata = [ 767 | {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, 768 | {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, 769 | ] 770 | isort = [ 771 | {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, 772 | {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, 773 | ] 774 | mccabe = [ 775 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 776 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 777 | ] 778 | mypy = [ 779 | {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"}, 780 | {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"}, 781 | {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"}, 782 | {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"}, 783 | {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"}, 784 | {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"}, 785 | {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"}, 786 | {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"}, 787 | {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"}, 788 | {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"}, 789 | {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"}, 790 | {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"}, 791 | {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"}, 792 | {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"}, 793 | {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"}, 794 | {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"}, 795 | {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"}, 796 | {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"}, 797 | {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"}, 798 | {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"}, 799 | {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"}, 800 | {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"}, 801 | {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"}, 802 | ] 803 | mypy-extensions = [ 804 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 805 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 806 | ] 807 | ossindex-lib = [ 808 | {file = "ossindex-lib-1.1.1.tar.gz", hash = "sha256:06eff2263a067aceee8fb45e11300c7d80863c5e9bb13016b866df40b30fe9eb"}, 809 | {file = "ossindex_lib-1.1.1-py3-none-any.whl", hash = "sha256:6c3fbf3ab03feb20f281d6971b5a5398ce3e6b637687090f338efb7ab08e424c"}, 810 | ] 811 | osv-lib = [ 812 | {file = "osv-lib-0.2.1.tar.gz", hash = "sha256:a75082b2301ce3d90e97317dbd165507d09427fa0d82a85bccdbda3c0d8f83ce"}, 813 | {file = "osv_lib-0.2.1-py3-none-any.whl", hash = "sha256:258f1b15bd22b3beb357ef90f865c1235189e35f120363242f32e1c4e4c70636"}, 814 | ] 815 | packageurl-python = [ 816 | {file = "packageurl-python-0.9.9.tar.gz", hash = "sha256:872a0434b9a448b3fa97571711f69dd2a3fb72345ad66c90b17d827afea82f09"}, 817 | {file = "packageurl_python-0.9.9-py3-none-any.whl", hash = "sha256:07aa852d1c48b0e86e625f6a32d83f96427739806b269d0f8142788ee807114b"}, 818 | ] 819 | packaging = [ 820 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 821 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 822 | ] 823 | platformdirs = [ 824 | {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, 825 | {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, 826 | ] 827 | pluggy = [ 828 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 829 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 830 | ] 831 | py = [ 832 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 833 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 834 | ] 835 | py-serializable = [ 836 | {file = "py-serializable-0.11.0.tar.gz", hash = "sha256:7a5834abd5bee8afa0bf3e39340af7e53e77285dff7323f417d4dc293b9d695e"}, 837 | {file = "py_serializable-0.11.0-py3-none-any.whl", hash = "sha256:245411c37ce816731bdb6e1a17d6e98a066f7abc6325c2052106cdc0af96d1c0"}, 838 | ] 839 | pycodestyle = [ 840 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 841 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 842 | ] 843 | pyflakes = [ 844 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 845 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 846 | ] 847 | pygments = [ 848 | {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, 849 | {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, 850 | ] 851 | pyyaml = [ 852 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 853 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 854 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 855 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 856 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 857 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 858 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 859 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 860 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 861 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 862 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 863 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 864 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 865 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 866 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 867 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 868 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 869 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 870 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 871 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 872 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 873 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 874 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 875 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 876 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 877 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 878 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 879 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 880 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 881 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 882 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 883 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 884 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 885 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 886 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 887 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 888 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 889 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 890 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 891 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 892 | ] 893 | requests = [ 894 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, 895 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, 896 | ] 897 | rich = [ 898 | {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, 899 | {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, 900 | ] 901 | six = [ 902 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 903 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 904 | ] 905 | sortedcontainers = [ 906 | {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, 907 | {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, 908 | ] 909 | tinydb = [ 910 | {file = "tinydb-4.7.1-py3-none-any.whl", hash = "sha256:1534e498ca23f55c43b0f1e7c0cf174049498ab45a887c82ba9831e0f9868df3"}, 911 | {file = "tinydb-4.7.1.tar.gz", hash = "sha256:8955c239a79b8a6c8f637900152e2de38690848199d71d870c33c16405433ca5"}, 912 | ] 913 | toml = [ 914 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 915 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 916 | ] 917 | tomli = [ 918 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 919 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 920 | ] 921 | tox = [ 922 | {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, 923 | {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, 924 | ] 925 | typed-ast = [ 926 | {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, 927 | {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, 928 | {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, 929 | {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, 930 | {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, 931 | {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, 932 | {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, 933 | {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, 934 | {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, 935 | {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, 936 | {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, 937 | {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, 938 | {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, 939 | {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, 940 | {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, 941 | {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, 942 | {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, 943 | {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, 944 | {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, 945 | {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, 946 | {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, 947 | {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, 948 | {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, 949 | {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, 950 | ] 951 | types-pyyaml = [ 952 | {file = "types-PyYAML-5.4.12.tar.gz", hash = "sha256:3f4daa754357491625ae8c3a39c9e1b0d7cd5632bc4e1c35e7a7f75a64aa124b"}, 953 | {file = "types_PyYAML-5.4.12-py3-none-any.whl", hash = "sha256:e06083f85375a5678e4c19452ed6467ce2167b71db222313e1792cb8fc76859a"}, 954 | ] 955 | types-requests = [ 956 | {file = "types-requests-2.28.11.15.tar.gz", hash = "sha256:fc8eaa09cc014699c6b63c60c2e3add0c8b09a410c818b5ac6e65f92a26dde09"}, 957 | {file = "types_requests-2.28.11.15-py3-none-any.whl", hash = "sha256:a05e4c7bc967518fba5789c341ea8b0c942776ee474c7873129a61161978e586"}, 958 | ] 959 | types-setuptools = [ 960 | {file = "types-setuptools-67.4.0.3.tar.gz", hash = "sha256:19e958dfdbf1c5a628e54c2a7ee84935051afb7278d0c1cdb08ac194757ee3b1"}, 961 | {file = "types_setuptools-67.4.0.3-py3-none-any.whl", hash = "sha256:3c83c3a6363dd3ddcdd054796705605f0fa8b8e5a39390e07a05e5f7af054978"}, 962 | ] 963 | types-urllib3 = [ 964 | {file = "types-urllib3-1.26.25.8.tar.gz", hash = "sha256:ecf43c42d8ee439d732a1110b4901e9017a79a38daca26f08e42c8460069392c"}, 965 | {file = "types_urllib3-1.26.25.8-py3-none-any.whl", hash = "sha256:95ea847fbf0bf675f50c8ae19a665baedcf07e6b4641662c4c3c72e7b2edf1a9"}, 966 | ] 967 | typing-extensions = [ 968 | {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, 969 | {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, 970 | ] 971 | urllib3 = [ 972 | {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, 973 | {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, 974 | ] 975 | virtualenv = [ 976 | {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, 977 | {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, 978 | ] 979 | zipp = [ 980 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 981 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 982 | ] 983 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "vexy" 3 | version = "0.3.1" 4 | description = "Generate VEX (Vulnerability Exploitability Exchange) CycloneDX documents" 5 | authors = ["Paul Horton "] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/madpah/vexy" 9 | repository = "https://github.com/madpah/vexy" 10 | packages = [ 11 | { include = "vexy" } 12 | ] 13 | include = [ 14 | "LICENSE", "NOTICE" 15 | ] 16 | classifiers = [ 17 | # Trove classifiers - https://packaging.python.org/specifications/core-metadata/#metadata-classifier 18 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 19 | 'Development Status :: 4 - Beta', 20 | 'Intended Audience :: Developers', 21 | 'Intended Audience :: Information Technology', 22 | 'Intended Audience :: Legal Industry', 23 | 'Intended Audience :: System Administrators', 24 | 'Topic :: Security', 25 | 'Topic :: Software Development', 26 | 'Topic :: System :: Software Distribution', 27 | 'License :: OSI Approved :: Apache Software License', 28 | 'Programming Language :: Python :: 3.7', 29 | 'Programming Language :: Python :: 3.8', 30 | 'Programming Language :: Python :: 3.9', 31 | 'Programming Language :: Python :: 3.10', 32 | 'Typing :: Typed' 33 | ] 34 | 35 | [tool.poetry.dependencies] 36 | python = "^3.7" 37 | # ATTENTION: keep `requirements.lowest.txt` file in sync 38 | cyclonedx-python-lib = ">= 4.0.0rc1, < 5.0.0" 39 | packageurl-python = ">= 0.9" 40 | importlib-metadata = { version = ">= 3.4", python = "< 3.8" } 41 | ossindex-lib = "^1.1.1" 42 | osv-lib = "^0.2.1" 43 | rich = "^12.4.4" 44 | 45 | [tool.poetry.dev-dependencies] 46 | autopep8 = "^1.6.0" 47 | isort = { version = "^5.10.0", python = ">= 3.6.1" } 48 | tox = "^3.25.0" 49 | coverage = "^6.2" 50 | mypy = "^0.961" 51 | flake8 = "^4.0.1" 52 | flake8-annotations = {version = "^2.7.0", python = ">= 3.6.2"} 53 | flake8-bugbear = "^22.6.22" 54 | flake8-isort = { version = "^4.1.0", python = ">= 3.6.1" } 55 | 56 | [tool.poetry.scripts] 57 | vexy = 'vexy.client:main' 58 | 59 | [build-system] 60 | requires = ["poetry-core>=1.0.0"] 61 | build-backend = "poetry.core.masonry.api" 62 | 63 | [tool.semantic_release] 64 | # https://python-semantic-release.readthedocs.io/en/latest/configuration.html 65 | version_variable = [ 66 | "pyproject.toml:version" 67 | ] 68 | branch = "main" 69 | upload_to_pypi = true 70 | upload_to_repository = true 71 | upload_to_release = true 72 | build_command = "pip install poetry && poetry build" 73 | remove_dist = false # dist results required for some CI automation 74 | -------------------------------------------------------------------------------- /requirements.lowest.txt: -------------------------------------------------------------------------------- 1 | # exactly pinned dependencies to the lowest version regardless of python_version 2 | # see pyptoject file for ranges 3 | 4 | cyclonedx-python-lib == 4.0.0rc1 5 | packageurl-python == 0.9 6 | importlib-metadata == 3.4.0 # ; python_version < '3.8' 7 | ossindex-lib == 1.1.1 8 | osv-lib == 0.2.1 9 | rich == 12.4.4 10 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | from os import path 21 | 22 | FIXTURES_DIRECTORY = path.join(path.dirname(__file__), 'fixtures') 23 | -------------------------------------------------------------------------------- /tests/test_source_ossindex.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | from unittest import TestCase 21 | 22 | from vexy.sources.ossindex import OssIndexSource 23 | 24 | 25 | class TestSourceOssIndex(TestCase): 26 | 27 | def test_source(self) -> None: 28 | self.assertGreater(len(OssIndexSource.source_ecosystems()), 0) 29 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # tox (https://tox.readthedocs.io/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | minversion = 3.10 8 | envlist = 9 | flake8 10 | mypy-{locked,lowest} 11 | py{310,39,38,37}-{locked,lowest} 12 | isolated_build = True 13 | skip_missing_interpreters = True 14 | usedevelop = False 15 | download = False 16 | 17 | [testenv] 18 | # settings in this category apply to all other testenv, if not overwritten 19 | skip_install = False 20 | allowlist_externals = poetry 21 | commands_pre = 22 | {envpython} --version 23 | poetry install --no-root -v 24 | lowest: poetry run pip install -U -r requirements.lowest.txt 25 | poetry run pip freeze 26 | commands = 27 | poetry run coverage run --source=vexy -m unittest -v 28 | 29 | [testenv:mypy{,-locked,-lowest}] 30 | skip_install = True 31 | commands = 32 | # mypy config is on own file: `.mypy.ini` 33 | !lowest: poetry run mypy 34 | lowest: poetry run mypy --python-version=3.7 35 | 36 | [testenv:flake8] 37 | skip_install = True 38 | commands = 39 | poetry run flake8 vexy/ tests/ 40 | 41 | [flake8] 42 | ## keep in sync with isort config - in `isort.cfg` file 43 | exclude = 44 | build,dist,__pycache__,.eggs,*.egg-info*, 45 | *_cache,*.cache, 46 | .git,.tox,.venv,venv 47 | _OLD,_TEST, 48 | docs 49 | max-line-length = 120 50 | ignore = E305, I003 51 | # ignore `self`, `cls` markers of flake8-annotations>=2.0 52 | ANN101,ANN102 53 | # ignore Opinionated Warnings - which are documented as disabled by default 54 | # See https://github.com/sco1/flake8-annotations#opinionated-warnings 55 | ANN401 56 | -------------------------------------------------------------------------------- /vexy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # This file is part of Vexy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # Copyright (c) Paul Horton. All Rights Reserved. 20 | 21 | import enum 22 | 23 | 24 | class EcoSystemType: 25 | 26 | def __init__(self, *, name: str, purl_type: str, description: str) -> None: 27 | self._name = name 28 | self._purl_type = purl_type 29 | self._description = description 30 | 31 | @property 32 | def name(self) -> str: 33 | return self._name 34 | 35 | @property 36 | def purl_type(self) -> str: 37 | return self._purl_type 38 | 39 | @property 40 | def description(self) -> str: 41 | return self._description 42 | 43 | 44 | _ALL_ECOSYSTEMS = { 45 | 'CARGO': EcoSystemType( 46 | name='Cargo', purl_type='cargo', description='The Rust community\'s crate registry' 47 | ), 48 | 'COCOAPODS': EcoSystemType( 49 | name='Cocoapods', purl_type='cocoapods', description='The Cocoa dependency manager' 50 | ), 51 | 'COMPOSER': EcoSystemType( 52 | name='Composer', purl_type='composer', description='Dependency Manager for PHP' 53 | ), 54 | 'CONAN': EcoSystemType( 55 | name='Conan', purl_type='conan', description='The open-source C/C++ package manager' 56 | ), 57 | 'CONDA': EcoSystemType( 58 | name='Conda', purl_type='conda', description='Conda is a cross-platform, language-agnostic binary package ' 59 | 'manager' 60 | ), 61 | 'CRAN': EcoSystemType( 62 | name='Cran', purl_type='cran', description='Comprehensive R Archive Network' 63 | ), 64 | 'GO': EcoSystemType( 65 | name='Go', purl_type='golang', description='Go Package Managers' 66 | ), 67 | 'MAVEN': EcoSystemType( 68 | name='Maven', purl_type='maven', description='Apache Maven' 69 | ), 70 | 'NPM': EcoSystemType( 71 | name='NPM', purl_type='npm', description='Package manager for the JavaScript programming language' 72 | ), 73 | 'NUGET': EcoSystemType( 74 | name='NuGet', purl_type='nuget', description='Microsoft NuGet' 75 | ), 76 | 'PYPI': EcoSystemType( 77 | name='PyPi', purl_type='pypi', description='Python Package Index' 78 | ), 79 | 'RPM': EcoSystemType( 80 | name='RPM', purl_type='rpm', description='Redhat Package Manager' 81 | ), 82 | 'RUBY_GEM': EcoSystemType( 83 | name='RubyGems', purl_type='gem', description='Ruby package system' 84 | ), 85 | 'SWIFT': EcoSystemType( 86 | name='Swift', purl_type='swift', description='Swift Package Manager' 87 | ) 88 | } 89 | 90 | 91 | class EcoSystem(enum.Enum): 92 | """ 93 | Languages/ecosystems to the PURL type 94 | 95 | Starting list taken from https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst 96 | """ 97 | 98 | BITBUCKET = 'BITBUCKET' 99 | CARGO = 'CARGO' 100 | COCOAPODS = 'COCOAPODS' 101 | COMPOSER = 'COMPOSER' 102 | CONAN = 'CONAN' 103 | CONDA = 'CONDA' 104 | CRAN = 'CRAN' 105 | DART = 'PUB' 106 | DEBIAN = 'DEB' 107 | DOCKER = 'DOCKER' 108 | FLUTTER = 'PUB' 109 | GENERIC = 'GENERIC' 110 | GITHUB = 'GITHUB' 111 | GO = 'GOLANG' 112 | HASKELL = 'HACKAGE' 113 | HEX = 'HEX' 114 | MAVEN = 'MAVEN' 115 | NPM = 'NPM' 116 | NUGET = 'NUGET' 117 | OCI = 'OCI' 118 | PYPI = 'PYPI' 119 | RPM = 'RPM' 120 | RUBY_GEM = 'GEM' 121 | SWIFT = 'SWIFT' 122 | 123 | def get_info(self) -> EcoSystemType: 124 | return _ALL_ECOSYSTEMS[self.value] 125 | -------------------------------------------------------------------------------- /vexy/__main__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | from .client import main 21 | 22 | main(prog_name=__package__) 23 | -------------------------------------------------------------------------------- /vexy/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # This file is part of Vexy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # Copyright (c) Paul Horton. All Rights Reserved. 20 | 21 | import argparse 22 | import enum 23 | import json 24 | import sys 25 | from datetime import datetime 26 | from importlib import import_module 27 | from io import TextIOWrapper 28 | from os import getcwd, path 29 | from string import printable 30 | from typing import Dict, Optional, Set, cast 31 | from urllib.parse import quote 32 | from xml.etree import ElementTree 33 | 34 | import yaml 35 | from cyclonedx.exception import CycloneDxException 36 | from cyclonedx.model import ExternalReference, ExternalReferenceType, Tool, XsUri 37 | from cyclonedx.model.bom import Bom 38 | from cyclonedx.output import BaseOutput 39 | from cyclonedx.schema import OutputFormat, SchemaVersion 40 | from rich.console import Console 41 | from rich.progress import Progress 42 | 43 | from vexy.sources import ALL_SOURCES 44 | from vexy.sources.base import BaseSource 45 | 46 | 47 | @enum.unique 48 | class _CLI_OUTPUT_FORMAT(enum.Enum): 49 | XML = 'xml' 50 | JSON = 'json' 51 | 52 | 53 | _output_formats: Dict[_CLI_OUTPUT_FORMAT, OutputFormat] = { 54 | _CLI_OUTPUT_FORMAT.XML: OutputFormat('Xml'), 55 | _CLI_OUTPUT_FORMAT.JSON: OutputFormat('Json'), 56 | } 57 | _output_default_filenames = { 58 | _CLI_OUTPUT_FORMAT.XML: 'cyclonedx-vex.xml', 59 | _CLI_OUTPUT_FORMAT.JSON: 'cyclonedx-vex.json', 60 | } 61 | 62 | if sys.version_info >= (3, 8): 63 | from importlib.metadata import version as meta_version 64 | else: 65 | from importlib_metadata import version as meta_version 66 | 67 | try: 68 | __ThisToolVersion: Optional[str] = str(meta_version('vexy')) # type: ignore[no-untyped-call] 69 | except Exception: 70 | __ThisToolVersion = None 71 | ThisTool = Tool(vendor='Vexy', name='vexy', version=__ThisToolVersion or 'UNKNOWN') 72 | ThisTool.external_references.update([ 73 | ExternalReference( 74 | type=ExternalReferenceType.BUILD_SYSTEM, 75 | url=XsUri('https://github.com/madpah/vexy/actions') 76 | ), 77 | ExternalReference( 78 | type=ExternalReferenceType.DISTRIBUTION, 79 | url=XsUri('https://pypi.org/project/vexy/') 80 | ), 81 | ExternalReference( 82 | type=ExternalReferenceType.DOCUMENTATION, 83 | url=XsUri('https://vexy.readthedocs.io/') 84 | ), 85 | ExternalReference( 86 | type=ExternalReferenceType.ISSUE_TRACKER, 87 | url=XsUri('https://github.com/madpah/vexy/issues') 88 | ), 89 | ExternalReference( 90 | type=ExternalReferenceType.LICENSE, 91 | url=XsUri('https://github.com/madpah/vexy/blob/main/LICENSE') 92 | ), 93 | ExternalReference( 94 | type=ExternalReferenceType.RELEASE_NOTES, 95 | url=XsUri('https://github.com/madpah/vexy/blob/main/CHANGELOG.md') 96 | ), 97 | ExternalReference( 98 | type=ExternalReferenceType.VCS, 99 | url=XsUri('https://github.com/madpah/vexy') 100 | ) 101 | ]) 102 | 103 | 104 | class VexyCmd: 105 | DEFAULT_CONFIG_FILE: str = '.vexy.config' 106 | 107 | # Whether debug output is enabled 108 | _DEBUG_ENABLED: bool = False 109 | 110 | # Parsed Arguments 111 | _arguments: argparse.Namespace 112 | 113 | def __init__(self, args: argparse.Namespace) -> None: 114 | self._arguments = args 115 | self._console = Console() 116 | 117 | if self._arguments.debug_enabled: 118 | self._DEBUG_ENABLED = True 119 | self._arguments.quiet_enabled = False 120 | self._debug_message('!!! DEBUG MODE ENABLED !!!') 121 | self._debug_message('Parsed Arguments: {}'.format(self._arguments)) 122 | 123 | self._data_sources: Set[BaseSource] = set() 124 | self._attempt_source_config_load(config=self._arguments.vexy_config) 125 | 126 | if not self._is_quiet(): 127 | self._console.print( 128 | f'Vexy is configured to use [bold cyan]{len(self._data_sources)}[/bold cyan] data sources.' 129 | ) 130 | 131 | def _attempt_source_config_load(self, config: TextIOWrapper) -> None: 132 | # Attempts to Vexy source configuration at the locations 133 | with config as config_f: 134 | vexy_config = yaml.safe_load(config_f.read()) 135 | for source_key, source_config in vexy_config['sources'].items(): 136 | self._data_sources.add(ALL_SOURCES[source_key](config=source_config)) 137 | 138 | def get_cli_output_format(self) -> _CLI_OUTPUT_FORMAT: 139 | return _CLI_OUTPUT_FORMAT(str(self._arguments.output_format).lower()) 140 | 141 | def _get_output_format(self) -> OutputFormat: 142 | return _output_formats[self.get_cli_output_format()] 143 | 144 | def _is_quiet(self) -> bool: 145 | return bool(self._arguments.quiet_enabled) 146 | 147 | def execute(self) -> None: 148 | with Progress() as progress: 149 | task_parse = progress.add_task( 150 | 'Parsing CycloneDX BOM for Components', total=100, visible=not self._is_quiet() 151 | ) 152 | progress.start_task(task_id=task_parse) 153 | 154 | # parser: BaseParser 155 | input_bom: Bom 156 | try: 157 | with self._arguments.input_source as input_bom_fh: 158 | if str(self._arguments.input_source.name).endswith('.json'): 159 | input_bom = Bom.from_json(data=json.loads(input_bom_fh.read())) # type: ignore[attr-defined] 160 | 161 | elif str(self._arguments.input_source.name).endswith('.xml'): 162 | input_bom = Bom.from_xml( # type: ignore[attr-defined] 163 | data=ElementTree.fromstring(input_bom_fh.read()) 164 | ) 165 | except CycloneDxException as e: 166 | print(f'Failure validating input BOM: {e}') 167 | return 168 | 169 | progress.update( 170 | task_id=task_parse, completed=100, 171 | description=f'Parsed {len(input_bom.components)} Components from CycloneDX SBOM' 172 | ) 173 | 174 | vex = Bom() 175 | vex.metadata.tools.add(ThisTool) 176 | data_source_tasks = {} 177 | for data_source in self._data_sources: 178 | data_source_tasks[data_source.__class__] = progress.add_task( 179 | f'Consulting {data_source.source_name()} for known vulnerabilities', total=100, 180 | visible=not self._is_quiet() 181 | ) 182 | data_source.process_components(components=input_bom.components) 183 | progress.update( 184 | task_id=data_source_tasks[data_source.__class__], completed=25, 185 | description=f'{data_source.source_name()}: Querying for {len(data_source.valid_components)} ' 186 | f'Components' 187 | ) 188 | vulnerabilities = data_source.get_vulnerabilities() 189 | progress.update( 190 | task_id=data_source_tasks[data_source.__class__], completed=50, 191 | description=f'{data_source.source_name()}: Processing Vulnerabilities for ' 192 | f'{len(data_source.valid_components)} Components' 193 | ) 194 | 195 | # @todo: CALL OUT ANY COMPONENTS THAT WERE NOT QUERIED 196 | 197 | i: int = 1 198 | for v in vulnerabilities: 199 | for a in v.affects: 200 | a.ref = f'{input_bom.urn()}#{quote(a.ref, safe=printable)}' 201 | vex.vulnerabilities.add(v) 202 | progress.update( 203 | task_id=data_source_tasks[data_source.__class__], 204 | completed=(50 + (i / len(vulnerabilities) * 50)) 205 | ) 206 | i += 1 207 | 208 | output_format = self._get_output_format() 209 | outputter = self._get_outputter(output_format=output_format, bom=vex) 210 | 211 | if self._arguments.output_file == '-' or not self._arguments.output_file: 212 | self._debug_message('Returning SBOM to STDOUT') 213 | print(outputter.output_as_string()) 214 | return 215 | 216 | # Check directory writable 217 | output_file = self._arguments.output_file 218 | output_filename = path.realpath( 219 | output_file if isinstance(output_file, str) else _output_default_filenames[self.get_cli_output_format()] 220 | ) 221 | self._debug_message('Will be outputting SBOM to file at: {}'.format(output_filename)) 222 | outputter.output_to_file(filename=output_filename, allow_overwrite=self._arguments.output_file_overwrite) 223 | 224 | def _get_outputter(self, output_format: OutputFormat, bom: Bom) -> BaseOutput: 225 | schema_version = SchemaVersion['V{}'.format( 226 | str(self._arguments.output_schema_version).replace('.', '_') 227 | )] 228 | try: 229 | module = import_module(f"cyclonedx.output.{self._arguments.output_format.lower()}") 230 | output_klass = getattr(module, f"{output_format.value}{schema_version.value}") 231 | except (ImportError, AttributeError): 232 | raise ValueError(f"Unknown format {output_format.value.lower()!r}") from None 233 | 234 | return cast(BaseOutput, output_klass(bom=bom)) 235 | 236 | @staticmethod 237 | def get_arg_parser(*, prog: Optional[str] = None) -> argparse.ArgumentParser: 238 | arg_parser = argparse.ArgumentParser(prog=prog, description='Vexy VEX Generator') 239 | 240 | arg_parser.add_argument( 241 | '-c', '--config', action='store', type=argparse.FileType('r'), # FileType does handle '-' 242 | dest='vexy_config', required=True, default=f'{getcwd()}/{VexyCmd.DEFAULT_CONFIG_FILE}', 243 | help='Configuration file for Vexy defining data sources to use and their configuration.' 244 | ) 245 | arg_parser.add_argument('-q', action='store_true', help='Quiet - no console output', dest='quiet_enabled') 246 | arg_parser.add_argument('-X', action='store_true', help='Enable debug output', dest='debug_enabled') 247 | 248 | input_method_group = arg_parser.add_argument_group( 249 | title='Input CycloneDX BOM', 250 | description='Where Vexy shall obtain it\'s input' 251 | ) 252 | input_method_group.add_argument( 253 | '-i', '--in-file', action='store', metavar='FILE_PATH', 254 | type=argparse.FileType('r'), # FileType does handle '-' 255 | default=None, dest='input_source', required=True, 256 | help='CycloneDX BOM to read input from. Use "-" to read from STDIN.' 257 | ) 258 | 259 | output_group = arg_parser.add_argument_group( 260 | title='VEX Output Configuration', 261 | description='Choose the output format and schema version' 262 | ) 263 | output_group.add_argument( 264 | '--format', action='store', 265 | choices=[f.value for f in _CLI_OUTPUT_FORMAT], default=_CLI_OUTPUT_FORMAT.XML.value, 266 | help='The output format for your SBOM (default: %(default)s)', 267 | dest='output_format' 268 | ) 269 | output_group.add_argument( 270 | '--schema-version', action='store', choices=['1.4'], default='1.4', 271 | help='The CycloneDX schema version for your VEX (default: %(default)s)', 272 | dest='output_schema_version' 273 | ) 274 | output_group.add_argument( 275 | '-o', '--o', '--output', action='store', metavar='FILE_PATH', default=True, required=False, 276 | help='Output file path for your SBOM (set to \'-\' to output to STDOUT)', dest='output_file' 277 | ) 278 | output_group.add_argument( 279 | '--force', action='store_true', dest='output_file_overwrite', 280 | help='If outputting to a file and the stated file already exists, it will be overwritten.' 281 | ) 282 | 283 | return arg_parser 284 | 285 | def _debug_message(self, message: str) -> None: 286 | if self._DEBUG_ENABLED: 287 | print('[DEBUG] - {} - {}'.format(datetime.now(), message)) 288 | 289 | @staticmethod 290 | def _error_and_exit(message: str, exit_code: int = 1) -> None: 291 | print('[ERROR] - {} - {}'.format(datetime.now(), message)) 292 | exit(exit_code) 293 | 294 | 295 | def main(*, prog_name: Optional[str] = None) -> None: 296 | parser = VexyCmd.get_arg_parser(prog=prog_name) 297 | args = parser.parse_args() 298 | VexyCmd(args).execute() 299 | 300 | 301 | if __name__ == "__main__": 302 | main() 303 | -------------------------------------------------------------------------------- /vexy/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561. This package uses inline types. 2 | # This file is needed to allow other packages to type-check their code against this package. 3 | -------------------------------------------------------------------------------- /vexy/sources/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # This file is part of Vexy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # Copyright (c) Paul Horton. All Rights Reserved. 20 | 21 | from typing import Dict, Type 22 | 23 | from .base import BaseSource 24 | from .ossindex import OssIndexSource 25 | from .osv import OsvSource 26 | 27 | ALL_SOURCES: Dict[str, Type[BaseSource]] = { 28 | 'ossindex': OssIndexSource, 29 | 'osv': OsvSource 30 | } 31 | -------------------------------------------------------------------------------- /vexy/sources/base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # This file is part of Vexy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # Copyright (c) Paul Horton. All Rights Reserved. 20 | 21 | from abc import ABC, abstractmethod 22 | from typing import Any, Dict, Iterable, Optional, Set 23 | 24 | from cyclonedx.model.component import Component 25 | from cyclonedx.model.vulnerability import Vulnerability, VulnerabilitySource 26 | 27 | from .. import EcoSystem 28 | 29 | 30 | class BaseSource(ABC): 31 | 32 | def __init__(self, *, config: Optional[Dict[str, Any]] = None) -> None: 33 | if config: 34 | self._configure_source(config=config) 35 | 36 | self._all_components: Set[Component] = set() 37 | self._valid_components: Set[Component] = set() 38 | 39 | def process_components(self, *, components: Iterable[Component]) -> None: 40 | self._all_components = set(components) 41 | self._valid_components = set(filter(lambda c: self._component_complete_for_source(component=c), components)) 42 | 43 | @property 44 | def all_components(self) -> Set[Component]: 45 | return self._all_components 46 | 47 | @property 48 | def valid_components(self) -> Set[Component]: 49 | return self._valid_components 50 | 51 | @abstractmethod 52 | def get_vulnerabilities(self) -> Set[Vulnerability]: 53 | pass 54 | 55 | @abstractmethod 56 | def _component_complete_for_source(self, *, component: Component) -> bool: 57 | """ 58 | Whether the given Component has enough data (the right fields) for us to query this data source for known 59 | vulnerabilities. 60 | 61 | :param component: Component 62 | :return: bool 63 | """ 64 | pass 65 | 66 | @abstractmethod 67 | def _configure_source(self, *, config: Dict[str, Any]) -> None: 68 | """ 69 | Perform any source specific configuration such as authentication. 70 | 71 | :param config: Dict[str, Any] 72 | :return: None 73 | """ 74 | pass 75 | 76 | @staticmethod 77 | @abstractmethod 78 | def source() -> VulnerabilitySource: 79 | """ 80 | Instance that represents this data source. 81 | 82 | :return: VulnerabilitySource 83 | """ 84 | pass 85 | 86 | @staticmethod 87 | @abstractmethod 88 | def source_name() -> str: 89 | """ 90 | Human-friendly name for this data source. 91 | 92 | :return: str 93 | """ 94 | pass 95 | 96 | @staticmethod 97 | @abstractmethod 98 | def source_description() -> str: 99 | """ 100 | Human-friendly description of this data source. 101 | 102 | :return: str 103 | """ 104 | pass 105 | 106 | @staticmethod 107 | @abstractmethod 108 | def source_ecosystems() -> Set[EcoSystem]: 109 | """ 110 | Which ecosystems this source has vulnerability data for. 111 | 112 | :return: Set[str] 113 | """ 114 | pass 115 | 116 | @staticmethod 117 | @abstractmethod 118 | def source_url() -> str: 119 | """ 120 | Public URL for this data source 121 | 122 | :return: str 123 | """ 124 | pass 125 | -------------------------------------------------------------------------------- /vexy/sources/ossindex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # This file is part of Vexy 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # Copyright (c) Paul Horton. All Rights Reserved. 20 | 21 | from decimal import Decimal 22 | from typing import Any, Dict, Set 23 | 24 | from cyclonedx.model import XsUri 25 | from cyclonedx.model.component import Component 26 | from cyclonedx.model.impact_analysis import ImpactAnalysisAffectedStatus 27 | from cyclonedx.model.vulnerability import ( 28 | BomTarget, 29 | BomTargetVersionRange, 30 | Vulnerability, 31 | VulnerabilityAdvisory, 32 | VulnerabilityRating, 33 | VulnerabilityReference, 34 | VulnerabilityScoreSource, 35 | VulnerabilitySeverity, 36 | VulnerabilitySource, 37 | ) 38 | from ossindex.ossindex import OssIndex 39 | 40 | from .. import EcoSystem 41 | from .base import BaseSource 42 | 43 | 44 | class OssIndexSource(BaseSource): 45 | 46 | def _component_complete_for_source(self, component: Component) -> bool: 47 | return component.purl is not None 48 | 49 | def _configure_source(self, config: Dict[str, Any]) -> None: 50 | pass 51 | 52 | def get_vulnerabilities(self) -> Set[Vulnerability]: 53 | ossi = OssIndex(enable_cache=False) 54 | ossi_results = ossi.get_component_report( 55 | packages=list(map(lambda c: c.purl, self.valid_components)) 56 | ) 57 | 58 | vulnerabilities: Set[Vulnerability] = set() 59 | 60 | for ossi_c in ossi_results: 61 | if ossi_c.vulnerabilities: 62 | for ossi_v in ossi_c.vulnerabilities: 63 | v_source = VulnerabilitySource( 64 | name=OssIndexSource.source_name(), url=XsUri(uri=ossi_v.reference) 65 | ) 66 | v = Vulnerability( 67 | source=OssIndexSource.source(), 68 | references=[ 69 | VulnerabilityReference(id=ossi_v.id, source=v_source) 70 | ], 71 | cwes=[int(ossi_v.cwe[4:])] if ossi_v.cwe else None, 72 | description=ossi_v.title, 73 | detail=ossi_v.description, 74 | affects=[ 75 | BomTarget( 76 | ref=ossi_c.get_package_url().to_string(), 77 | versions=[ 78 | BomTargetVersionRange( 79 | version=ossi_c.get_package_url().version, 80 | status=ImpactAnalysisAffectedStatus.UNKNOWN 81 | ) 82 | ] 83 | ) 84 | ] 85 | ) 86 | 87 | if ossi_v.cvss_score: 88 | v.ratings.add( 89 | VulnerabilityRating( 90 | source=v_source, score=Decimal( 91 | ossi_v.cvss_score 92 | ) if ossi_v.cvss_score else None, 93 | severity=VulnerabilitySeverity.get_from_cvss_scores( 94 | (ossi_v.cvss_score,) 95 | ) if ossi_v.cvss_score else None, 96 | method=VulnerabilityScoreSource.get_from_vector( 97 | vector=ossi_v.cvss_vector 98 | ) if ossi_v.cvss_vector else None, 99 | vector=ossi_v.cvss_vector) 100 | ) 101 | 102 | for ext_ref in ossi_v.external_references: 103 | v.advisories.add(VulnerabilityAdvisory(url=XsUri(uri=ext_ref))) 104 | 105 | vulnerabilities.add(v) 106 | 107 | return vulnerabilities 108 | 109 | @staticmethod 110 | def source() -> VulnerabilitySource: 111 | """ 112 | Instance that represents this data source. 113 | 114 | :return: VulnerabilitySource 115 | """ 116 | return VulnerabilitySource(name=OssIndexSource.source_name(), url=XsUri(uri=OssIndexSource.source_url())) 117 | 118 | @staticmethod 119 | def source_name() -> str: 120 | return 'OSS Index by Sonatype' 121 | 122 | @staticmethod 123 | def source_description() -> str: 124 | return 'OSS Index is a free service used by developers to identify open source dependencies and determine if ' \ 125 | 'there are any known, publicly disclosed, vulnerabilities. OSS Index is based on vulnerability data ' \ 126 | 'derived from public sources and does not include human curated intelligence nor expert remediation ' \ 127 | 'guidance.' 128 | 129 | @staticmethod 130 | def source_ecosystems() -> Set[EcoSystem]: 131 | return { 132 | EcoSystem.CARGO, EcoSystem.COCOAPODS, EcoSystem.COMPOSER, EcoSystem.COMPOSER, EcoSystem.CONAN, 133 | EcoSystem.CONDA, EcoSystem.CRAN, EcoSystem.GO, EcoSystem.MAVEN, EcoSystem.NPM, EcoSystem.NUGET, 134 | EcoSystem.PYPI, EcoSystem.RPM, EcoSystem.RUBY_GEM, EcoSystem.SWIFT 135 | } 136 | 137 | @staticmethod 138 | def source_url() -> str: 139 | """ 140 | Public URL for this data source 141 | 142 | :return: str 143 | """ 144 | return 'https://ossindex.sonatype.org/' 145 | -------------------------------------------------------------------------------- /vexy/sources/osv.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | from typing import Any, Dict, List, Set 21 | 22 | from cyclonedx.model import OrganizationalContact, XsUri 23 | from cyclonedx.model.component import Component 24 | from cyclonedx.model.impact_analysis import ImpactAnalysisAffectedStatus 25 | from cyclonedx.model.vulnerability import ( 26 | BomTarget, 27 | BomTargetVersionRange, 28 | Vulnerability, 29 | VulnerabilityAdvisory, 30 | VulnerabilityCredits, 31 | VulnerabilityRating, 32 | VulnerabilityReference, 33 | VulnerabilitySource, 34 | ) 35 | from osv.api import OsvApi 36 | from osv.model import OsvPackage, OsvReferenceType, OsvSeverityType 37 | 38 | from .. import EcoSystem 39 | from .base import BaseSource 40 | 41 | 42 | class OsvSource(BaseSource): 43 | 44 | def get_vulnerabilities(self) -> Set[Vulnerability]: 45 | osv = OsvApi() 46 | 47 | vulnerabilities: Set[Vulnerability] = set() 48 | 49 | for component in self.valid_components: 50 | osv_vulnerabilities = osv.query(package=OsvPackage(purl=component.purl)) 51 | for osv_v in osv_vulnerabilities: 52 | affected_versions: List[BomTargetVersionRange] = [] 53 | for affected in osv_v.affected: 54 | if affected.ranges: 55 | for r in affected.ranges: 56 | affected_versions.append(BomTargetVersionRange( 57 | range=r.as_purl_vers(), status=ImpactAnalysisAffectedStatus.AFFECTED 58 | )) 59 | for v in affected.versions: 60 | affected_versions.append(BomTargetVersionRange( 61 | version=v, status=ImpactAnalysisAffectedStatus.AFFECTED 62 | )) 63 | 64 | ratings: List[VulnerabilityRating] = [] 65 | for severity in osv_v.severity: 66 | if severity.type_ == OsvSeverityType.CVSS_V3: 67 | ratings.append(VulnerabilityRating( 68 | vector=severity.score 69 | )) 70 | 71 | advisories: List[VulnerabilityAdvisory] = [] 72 | for ref in osv_v.references: 73 | if ref.type_ == OsvReferenceType.ADVISORY: 74 | advisories.append(VulnerabilityAdvisory(url=XsUri(ref.url))) 75 | 76 | credits_ = None 77 | if osv_v.credits: 78 | credit = osv_v.credits.pop() 79 | contact_1 = credit.contact.pop() 80 | credits_ = VulnerabilityCredits(individuals=[ 81 | OrganizationalContact( 82 | name=credit.name, 83 | phone=contact_1 if '@' not in contact_1 else None, 84 | email=contact_1 if '@' in contact_1 else None 85 | ) 86 | ]) 87 | 88 | vulnerabilities.add( 89 | Vulnerability( 90 | source=OsvSource.source(), 91 | references=[ 92 | VulnerabilityReference(id=osv_v.id_, source=OsvSource.source()) 93 | ], 94 | ratings=ratings, 95 | cwes=None, 96 | description=osv_v.summary, 97 | detail=osv_v.details, 98 | advisories=advisories, 99 | created=osv_v.published, 100 | published=osv_v.published, 101 | updated=osv_v.modified if osv_v.modified else None, 102 | credits=credits_, 103 | affects=[ 104 | BomTarget( 105 | ref=component.bom_ref.value, 106 | versions=affected_versions 107 | ) 108 | ], 109 | ) 110 | ) 111 | 112 | return vulnerabilities 113 | 114 | def _component_complete_for_source(self, *, component: Component) -> bool: 115 | return component.purl is not None 116 | 117 | def _configure_source(self, *, config: Dict[str, Any]) -> None: 118 | pass 119 | 120 | @staticmethod 121 | def source() -> VulnerabilitySource: 122 | """ 123 | Instance that represents this data source. 124 | 125 | :return: VulnerabilitySource 126 | """ 127 | return VulnerabilitySource(name=OsvSource.source_name(), url=XsUri(uri=OsvSource.source_url())) 128 | 129 | @staticmethod 130 | def source_name() -> str: 131 | return 'OSV' 132 | 133 | @staticmethod 134 | def source_description() -> str: 135 | return 'An open, precise, and distributed approach to producing and consuming vulnerability information for ' \ 136 | 'open source.' 137 | 138 | @staticmethod 139 | def source_ecosystems() -> Set[EcoSystem]: 140 | return { 141 | EcoSystem.CARGO, EcoSystem.COMPOSER, EcoSystem.DEBIAN, EcoSystem.GO, EcoSystem.MAVEN, EcoSystem.NPM, 142 | EcoSystem.NUGET, EcoSystem.PYPI, EcoSystem.RPM, EcoSystem.RUBY_GEM 143 | } 144 | 145 | @staticmethod 146 | def source_url() -> str: 147 | """ 148 | Public URL for this data source 149 | 150 | :return: str 151 | """ 152 | return 'https://osv.dev/' 153 | -------------------------------------------------------------------------------- /vexy/sources/osvdb.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is part of Vexy 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # SPDX-License-Identifier: Apache-2.0 18 | # Copyright (c) Paul Horton. All Rights Reserved. 19 | 20 | from typing import Any, Dict, Set 21 | 22 | import requests 23 | from cyclonedx.model import XsUri 24 | from cyclonedx.model.component import Component 25 | from cyclonedx.model.vulnerability import Vulnerability, VulnerabilitySource 26 | 27 | from .. import EcoSystem 28 | from .base import BaseSource 29 | 30 | 31 | class OsvDbSource(BaseSource): 32 | 33 | def get_vulnerabilities(self) -> Set[Vulnerability]: 34 | vulnerabilities: Set[Vulnerability] = set() 35 | total = 0 36 | 37 | for component in self.valid_components: 38 | if component.purl: 39 | print(f'Querying OSV DB for known vulnerabilities for {component.purl.to_string()}') 40 | vulns = requests.post('http://127.0.0.1:8888/api/v1/query', json={ 41 | 'purl': component.purl.to_string() 42 | }).json() 43 | 44 | total = total + len(vulns["results"]) 45 | # for osv_v in osv_vulnerabilities: 46 | # affected_versions: List[BomTargetVersionRange] = [] 47 | # for affected in osv_v.affected: 48 | # if affected.ranges: 49 | # for r in affected.ranges: 50 | # affected_versions.append(BomTargetVersionRange( 51 | # version_range=r.as_purl_vers(), status=ImpactAnalysisAffectedStatus.AFFECTED 52 | # )) 53 | # for v in affected.versions: 54 | # affected_versions.append(BomTargetVersionRange( 55 | # version=v, status=ImpactAnalysisAffectedStatus.AFFECTED 56 | # )) 57 | # 58 | # ratings: List[VulnerabilityRating] = [] 59 | # for severity in osv_v.severity: 60 | # if severity.type_ == OsvSeverityType.CVSS_V3: 61 | # ratings.append(VulnerabilityRating( 62 | # vector=severity.score 63 | # )) 64 | # 65 | # advisories: List[VulnerabilityAdvisory] = [] 66 | # for ref in osv_v.references: 67 | # if ref.type_ == OsvReferenceType.ADVISORY: 68 | # advisories.append(VulnerabilityAdvisory(url=XsUri(ref.url))) 69 | # 70 | # credits_ = None 71 | # if osv_v.credits: 72 | # credit = osv_v.credits.pop() 73 | # contact_1 = credit.contact.pop() 74 | # credits_ = VulnerabilityCredits(individuals=[ 75 | # OrganizationalContact( 76 | # name=credit.name, 77 | # phone=contact_1 if '@' not in contact_1 else None, 78 | # email=contact_1 if '@' in contact_1 else None 79 | # ) 80 | # ]) 81 | # 82 | # vulnerabilities.add( 83 | # Vulnerability( 84 | # source=OsvSource.source(), 85 | # references=[ 86 | # VulnerabilityReference(id=osv_v.id_, source=OsvSource.source()) 87 | # ], 88 | # ratings=ratings, 89 | # cwes=None, 90 | # description=osv_v.summary, 91 | # detail=osv_v.details, 92 | # advisories=advisories, 93 | # created=osv_v.published, 94 | # published=osv_v.published, 95 | # updated=osv_v.modified if osv_v.modified else None, 96 | # credits=credits_, 97 | # affects_targets=[ 98 | # BomTarget( 99 | # ref=component.bom_ref.value, 100 | # versions=affected_versions 101 | # ) 102 | # ], 103 | # ) 104 | # ) 105 | 106 | print(f'Total Vulns: {total}') 107 | 108 | return vulnerabilities 109 | 110 | def _component_complete_for_source(self, *, component: Component) -> bool: 111 | return component.purl is not None 112 | 113 | def _configure_source(self, *, config: Dict[str, Any]) -> None: 114 | pass 115 | 116 | @staticmethod 117 | def source() -> VulnerabilitySource: 118 | """ 119 | Instance that represents this data source. 120 | 121 | :return: VulnerabilitySource 122 | """ 123 | return VulnerabilitySource(name=OsvDbSource.source_name(), url=XsUri(uri=OsvDbSource.source_url())) 124 | 125 | @staticmethod 126 | def source_name() -> str: 127 | return 'OSV DB' 128 | 129 | @staticmethod 130 | def source_description() -> str: 131 | return 'An open, precise, and distributed approach to producing and consuming vulnerability information for ' \ 132 | 'open source.' 133 | 134 | @staticmethod 135 | def source_ecosystems() -> Set[EcoSystem]: 136 | return { 137 | EcoSystem.DEBIAN 138 | } 139 | 140 | @staticmethod 141 | def source_url() -> str: 142 | """ 143 | Public URL for this data source 144 | 145 | :return: str 146 | """ 147 | return 'http://127.0.0.1:8888/' 148 | --------------------------------------------------------------------------------