├── .gitchangelog.rc ├── .github └── workflows │ ├── codeql.yml │ └── pythonapp.yml ├── .gitignore ├── AUTHORS ├── README.md ├── _config.yml ├── bin └── finder.py ├── doc └── logos │ ├── git-vuln-finder-small.png │ ├── git-vuln-finder.png │ └── git-vuln-finder.svg ├── git_vuln_finder ├── __init__.py ├── pattern.py ├── patterns │ └── en │ │ └── medium │ │ ├── c │ │ ├── c.prefix │ │ ├── c.suffix │ │ ├── crypto │ │ ├── crypto.prefix │ │ ├── crypto.suffix │ │ ├── vuln │ │ ├── vuln.prefix │ │ └── vuln.suffix ├── run.py └── vulnerability.py ├── poetry.lock ├── pyproject.toml └── tests ├── conftest.py ├── gharchive_test.json ├── sample_output.json └── test_finder.py /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | ## 3 | ## Format 4 | ## 5 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 6 | ## 7 | ## Description 8 | ## 9 | ## ACTION is one of 'chg', 'fix', 'new' 10 | ## 11 | ## Is WHAT the change is about. 12 | ## 13 | ## 'chg' is for refactor, small improvement, cosmetic changes... 14 | ## 'fix' is for bug fixes 15 | ## 'new' is for new features, big improvement 16 | ## 17 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc'|'docs' 18 | ## 19 | ## Is WHO is concerned by the change. 20 | ## 21 | ## 'dev' is for developpers (API changes, refactors...) 22 | ## 'usr' is for final users (UI changes) 23 | ## 'pkg' is for packagers (packaging changes) 24 | ## 'test' is for testers (test only related changes) 25 | ## 'doc' is for doc guys (doc only changes) 26 | ## 27 | ## COMMIT_MSG is ... well ... the commit message itself. 28 | ## 29 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 30 | ## 31 | ## They are preceded with a '!' or a '@' (prefer the former, as the 32 | ## latter is wrongly interpreted in github.) Commonly used tags are: 33 | ## 34 | ## 'refactor' is obviously for refactoring code only 35 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 36 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 37 | ## 'wip' is for partial functionality but complete subfunctionality. 38 | ## 39 | ## Example: 40 | ## 41 | ## new: usr: support of bazaar implemented 42 | ## chg: re-indentend some lines !cosmetic 43 | ## new: dev: updated code to be compatible with last version of killer lib. 44 | ## fix: pkg: updated year of licence coverage. 45 | ## new: test: added a bunch of test around user usability of feature X. 46 | ## fix: typo in spelling my name in comment. !minor 47 | ## 48 | ## Please note that multi-line commit message are supported, and only the 49 | ## first line will be considered as the "summary" of the commit message. So 50 | ## tags, and other rules only applies to the summary. The body of the commit 51 | ## message will be displayed in the changelog without reformatting. 52 | 53 | 54 | ## 55 | ## ``ignore_regexps`` is a line of regexps 56 | ## 57 | ## Any commit having its full commit message matching any regexp listed here 58 | ## will be ignored and won't be reported in the changelog. 59 | ## 60 | ignore_regexps = [ 61 | r'@minor', r'!minor', 62 | r'@cosmetic', r'!cosmetic', 63 | r'@refactor', r'!refactor', 64 | r'@wip', r'!wip', 65 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 66 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', 67 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 68 | ] 69 | 70 | 71 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 72 | ## list of regexp 73 | ## 74 | ## Commit messages will be classified in sections thanks to this. Section 75 | ## titles are the label, and a commit is classified under this section if any 76 | ## of the regexps associated is matching. 77 | ## 78 | ## Please note that ``section_regexps`` will only classify commits and won't 79 | ## make any changes to the contents. So you'll probably want to go check 80 | ## ``subject_process`` (or ``body_process``) to do some changes to the subject, 81 | ## whenever you are tweaking this variable. 82 | ## 83 | section_regexps = [ 84 | ('New', [ 85 | r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc|docs)\s*:\s*)?([^\n]*)$', 86 | ]), 87 | ('Changes', [ 88 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc|docs)\s*:\s*)?([^\n]*)$', 89 | ]), 90 | ('Fix', [ 91 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc|docs)\s*:\s*)?([^\n]*)$', 92 | ]), 93 | 94 | ('Other', None ## Match all lines 95 | ), 96 | 97 | ] 98 | 99 | 100 | ## ``body_process`` is a callable 101 | ## 102 | ## This callable will be given the original body and result will 103 | ## be used in the changelog. 104 | ## 105 | ## Available constructs are: 106 | ## 107 | ## - any python callable that take one txt argument and return txt argument. 108 | ## 109 | ## - ReSub(pattern, replacement): will apply regexp substitution. 110 | ## 111 | ## - Indent(chars=" "): will indent the text with the prefix 112 | ## Please remember that template engines gets also to modify the text and 113 | ## will usually indent themselves the text if needed. 114 | ## 115 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 116 | ## 117 | ## - noop: do nothing 118 | ## 119 | ## - ucfirst: ensure the first letter is uppercase. 120 | ## (usually used in the ``subject_process`` pipeline) 121 | ## 122 | ## - final_dot: ensure text finishes with a dot 123 | ## (usually used in the ``subject_process`` pipeline) 124 | ## 125 | ## - strip: remove any spaces before or after the content of the string 126 | ## 127 | ## - SetIfEmpty(msg="No commit message."): will set the text to 128 | ## whatever given ``msg`` if the current text is empty. 129 | ## 130 | ## Additionally, you can `pipe` the provided filters, for instance: 131 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 132 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 133 | #body_process = noop 134 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 135 | 136 | 137 | ## ``subject_process`` is a callable 138 | ## 139 | ## This callable will be given the original subject and result will 140 | ## be used in the changelog. 141 | ## 142 | ## Available constructs are those listed in ``body_process`` doc. 143 | subject_process = (strip | 144 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc|docs)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 145 | SetIfEmpty("No commit message.") | ucfirst | final_dot) 146 | 147 | 148 | ## ``tag_filter_regexp`` is a regexp 149 | ## 150 | ## Tags that will be used for the changelog must match this regexp. 151 | ## 152 | tag_filter_regexp = r'^v[0-9]+\.[0-9]$' 153 | 154 | 155 | 156 | ## ``unreleased_version_label`` is a string or a callable that outputs a string 157 | ## 158 | ## This label will be used as the changelog Title of the last set of changes 159 | ## between last valid tag and HEAD if any. 160 | unreleased_version_label = "%%version%% (unreleased)" 161 | 162 | 163 | ## ``output_engine`` is a callable 164 | ## 165 | ## This will change the output format of the generated changelog file 166 | ## 167 | ## Available choices are: 168 | ## 169 | ## - rest_py 170 | ## 171 | ## Legacy pure python engine, outputs ReSTructured text. 172 | ## This is the default. 173 | ## 174 | ## - mustache() 175 | ## 176 | ## Template name could be any of the available templates in 177 | ## ``templates/mustache/*.tpl``. 178 | ## Requires python package ``pystache``. 179 | ## Examples: 180 | ## - mustache("markdown") 181 | ## - mustache("restructuredtext") 182 | ## 183 | ## - makotemplate() 184 | ## 185 | ## Template name could be any of the available templates in 186 | ## ``templates/mako/*.tpl``. 187 | ## Requires python package ``mako``. 188 | ## Examples: 189 | ## - makotemplate("restructuredtext") 190 | ## 191 | #output_engine = rest_py 192 | #output_engine = mustache("restructuredtext") 193 | output_engine = mustache("markdown") 194 | #output_engine = makotemplate("restructuredtext") 195 | 196 | 197 | ## ``include_merge`` is a boolean 198 | ## 199 | ## This option tells git-log whether to include merge commits in the log. 200 | ## The default is to include them. 201 | include_merge = True 202 | 203 | 204 | ## ``log_encoding`` is a string identifier 205 | ## 206 | ## This option tells gitchangelog what encoding is outputed by ``git log``. 207 | ## The default is to be clever about it: it checks ``git config`` for 208 | ## ``i18n.logOutputEncoding``, and if not found will default to git's own 209 | ## default: ``utf-8``. 210 | #log_encoding = 'utf-8' 211 | 212 | 213 | ## ``publish`` is a callable 214 | ## 215 | ## Sets what ``gitchangelog`` should do with the output generated by 216 | ## the output engine. ``publish`` is a callable taking one argument 217 | ## that is an interator on lines from the output engine. 218 | ## 219 | ## Some helper callable are provided: 220 | ## 221 | ## Available choices are: 222 | ## 223 | ## - stdout 224 | ## 225 | ## Outputs directly to standard output 226 | ## (This is the default) 227 | ## 228 | ## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start()) 229 | ## 230 | ## Creates a callable that will parse given file for the given 231 | ## regex pattern and will insert the output in the file. 232 | ## ``idx`` is a callable that receive the matching object and 233 | ## must return a integer index point where to insert the 234 | ## the output in the file. Default is to return the position of 235 | ## the start of the matched string. 236 | ## 237 | ## - FileRegexSubst(file, pattern, replace, flags) 238 | ## 239 | ## Apply a replace inplace in the given file. Your regex pattern must 240 | ## take care of everything and might be more complex. Check the README 241 | ## for a complete copy-pastable example. 242 | ## 243 | # publish = FileInsertIntoFirstRegexMatch( 244 | # "CHANGELOG.rst", 245 | # r'/(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/', 246 | # idx=lambda m: m.start(1) 247 | # ) 248 | #publish = stdout 249 | 250 | 251 | ## ``revs`` is a list of callable or a list of string 252 | ## 253 | ## callable will be called to resolve as strings and allow dynamical 254 | ## computation of these. The result will be used as revisions for 255 | ## gitchangelog (as if directly stated on the command line). This allows 256 | ## to filter exaclty which commits will be read by gitchangelog. 257 | ## 258 | ## To get a full documentation on the format of these strings, please 259 | ## refer to the ``git rev-list`` arguments. There are many examples. 260 | ## 261 | ## Using callables is especially useful, for instance, if you 262 | ## are using gitchangelog to generate incrementally your changelog. 263 | ## 264 | ## Some helpers are provided, you can use them:: 265 | ## 266 | ## - FileFirstRegexMatch(file, pattern): will return a callable that will 267 | ## return the first string match for the given pattern in the given file. 268 | ## If you use named sub-patterns in your regex pattern, it'll output only 269 | ## the string matching the regex pattern named "rev". 270 | ## 271 | ## - Caret(rev): will return the rev prefixed by a "^", which is a 272 | ## way to remove the given revision and all its ancestor. 273 | ## 274 | ## Please note that if you provide a rev-list on the command line, it'll 275 | ## replace this value (which will then be ignored). 276 | ## 277 | ## If empty, then ``gitchangelog`` will act as it had to generate a full 278 | ## changelog. 279 | ## 280 | ## The default is to use all commits to make the changelog. 281 | #revs = ["^1.0.3", ] 282 | #revs = [ 283 | # Caret( 284 | # FileFirstRegexMatch( 285 | # "CHANGELOG.rst", 286 | # r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), 287 | # "HEAD" 288 | #] 289 | revs = [] 290 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '32 5 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | name: Python application 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Set up Python 3.8 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: 3.8 17 | 18 | - name: Install dependencies 19 | run: | 20 | pip install poetry 21 | poetry install 22 | 23 | - name: Lint with flake8 24 | run: | 25 | pip install flake8 26 | # stop the build if there are Python syntax errors or undefined names 27 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 28 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 29 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 30 | 31 | - name: Test with pytest 32 | run: | 33 | poetry run pytest 34 | env: 35 | testing: actions 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # use glob syntax 2 | syntax: glob 3 | 4 | *.elc 5 | *.pyc 6 | *~ 7 | *.db 8 | 9 | # Virtualenv 10 | venv 11 | build 12 | 13 | # setuptools 14 | build/* 15 | git_vuln_finder.egg-info/ 16 | dist/* 17 | 18 | # tests 19 | .coverage 20 | .mypy_cache/ 21 | .cache/ 22 | test_repos/ 23 | 24 | # sphinx 25 | docs/_build 26 | 27 | # Emacs 28 | eproject.cfg 29 | 30 | # Temporary files (vim backups) 31 | *.swp 32 | 33 | .idea/ 34 | 35 | # Log files: 36 | *.log 37 | 38 | # Vagrant: 39 | .vagrant/ 40 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Cedric Bonhomme 2 | David Cruciani 3 | Alexandre Dulaunoy 4 | Jean-Louis Huynen 5 | Sebastien Tricaud 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-vuln-finder 2 | 3 | ![git-vuln-finder logo](https://raw.githubusercontent.com/cve-search/git-vuln-finder/f22077452c37e110bff0564e1f7b34637dc726c3/doc/logos/git-vuln-finder-small.png) 4 | 5 | [![Workflow](https://github.com/cve-search/git-vuln-finder/workflows/Python%20application/badge.svg)](https://github.com/cve-search/git-vuln-finder/actions?query=workflow%3A%22Python+application%22) 6 | 7 | Finding potential software vulnerabilities from git commit messages. 8 | The output format is a JSON with the associated commit which could contain a 9 | fix regarding a software vulnerability. The search is based on a set of regular 10 | expressions against the commit messages only. If CVE IDs are present, those are 11 | added automatically in the output. The input can be any git repositories or 12 | a [GH archive source](https://www.gharchive.org/). 13 | 14 | # Requirements 15 | 16 | - jq (``sudo apt install jq``) 17 | - Python poetry 18 | 19 | # Installation 20 | 21 | ## Use it as a library 22 | 23 | git-vuln-finder can be install with poetry. If you don't have poetry installed, you can do the following `curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python`. 24 | 25 | ~~~bash 26 | $ poetry install 27 | $ poetry shell 28 | $ git-vuln-finder -h 29 | ~~~ 30 | 31 | You can also use ``pip``. Then just import it: 32 | 33 | ~~~python 34 | Python 3.8.0 (default, Dec 11 2019, 21:43:13) 35 | [GCC 9.2.1 20191008] on linux 36 | Type "help", "copyright", "credits" or "license" for more information. 37 | >>> from git_vuln_finder import find 38 | >>> all_potential_vulnerabilities, all_cve_found, found = find("~/git/curl") 39 | 40 | >>> [commit for commit, summary in all_potential_vulnerabilities.items() if summary['state'] == 'cve-assigned'] 41 | ['9069838b30fb3b48af0123e39f664cea683254a5', 'facb0e4662415b5f28163e853dc6742ac5fafb3d', 42 | ... snap ... 43 | '8a75dbeb2305297640453029b7905ef51b87e8dd', '1dc43de0dccc2ea7da6dddb7b98f8d7dcf323914', '192c4f788d48f82c03e9cef40013f34370e90737', '2eb8dcf26cb37f09cffe26909a646e702dbcab66', 'fa1ae0abcde5df8d0b3283299e3f246bedf7692c', 'c11c30a8c8d727dcf5634fa0cc6ee0b4b77ddc3d', '75ca568fa1c19de4c5358fed246686de8467c238', 'a20daf90e358c1476a325ea665d533f7a27e3364', '042cc1f69ec0878f542667cb684378869f859911'] 44 | 45 | >>> print(json.dumps(all_potential_vulnerabilities['9069838b30fb3b48af0123e39f664cea683254a5'], sort_keys=True, indent=4, separators=(",", ": "))) 46 | { 47 | "author": "Daniel Stenberg", 48 | "author-email": "daniel@haxx.se", 49 | "authored_date": 1567544372, 50 | "branches": [ 51 | "master" 52 | ], 53 | "commit-id": "9069838b30fb3b48af0123e39f664cea683254a5", 54 | "committed_date": 1568009674, 55 | "cve": [ 56 | "CVE-2019-5481", 57 | "CVE-2019-5481" 58 | ], 59 | "language": "en", 60 | "message": "security:read_data fix bad realloc()\n\n... that could end up a double-free\n\nCVE-2019-5481\nBug: https://curl.haxx.se/docs/CVE-2019-5481.html\n", 61 | "origin": "https://github.com/curl/curl.git", 62 | "origin-github-api": "https://api.github.com/repos///github.com/curl/curl/commits/9069838b30fb3b48af0123e39f664cea683254a5", 63 | "pattern-matches": [ 64 | "double-free" 65 | ], 66 | "pattern-selected": "(?i)(double[-| ]free|buffer overflow|double free|race[-| ]condition)", 67 | "state": "cve-assigned", 68 | "stats": { 69 | "deletions": 4, 70 | "files": 1, 71 | "insertions": 2, 72 | "lines": 6 73 | }, 74 | "summary": "security:read_data fix bad realloc()", 75 | "tags": [] 76 | } 77 | ~~~ 78 | 79 | 80 | ## Use it as a command line tool 81 | 82 | ~~~bash 83 | $ git clone https://github.com/cve-search/git-vuln-finder.git 84 | $ cd https://github.com/cve-search/git-vuln-finder.git 85 | $ pip install . 86 | $ git-vuln-finder --help 87 | ~~~ 88 | 89 | You can also use pip. 90 | ``pipx`` installs scripts (system wide available) provided by Python packages 91 | into separate virtualenvs to shield them from your system and each other. 92 | 93 | 94 | ### Usage 95 | 96 | ~~~bash 97 | usage: git-vuln-finder [-h] [-v] [-r R] [-o O] [-s S] [-p P] [-c] [-t] [-gh GH] 98 | 99 | Finding potential software vulnerabilities from git commit messages. 100 | 101 | optional arguments: 102 | -h, --help show this help message and exit 103 | -v increase output verbosity 104 | -r R git repository to analyse 105 | -o O Output format: [json] 106 | -s S State of the commit found 107 | -p P Matching pattern to use: [vulnpatterns, cryptopatterns, cpatterns] - the pattern 'all' is used to match all the patterns at once. 108 | -c output only a list of the CVE pattern found in commit messages (disable by default) 109 | -t Include tags matching a specific commit 110 | -gh GH special option for gharchive, pass a file containing a PushEvent in JSON format 111 | 112 | More info: https://github.com/cve-search/git-vuln-finder 113 | ~~~ 114 | 115 | 116 | # Patterns 117 | 118 | git-vuln-finder comes with 3 default patterns which can be selected to find the potential vulnerabilities described in the commit messages such as: 119 | 120 | - [`vulnpatterns`](git_vuln_finder/patterns/en/medium/vuln) is a generic vulnerability pattern especially targeting web application and generic security commit message. Based on an academic paper. 121 | - [`cryptopatterns`](git_vuln_finder/patterns/en/medium/crypto) is a vulnerability pattern for cryptographic errors mentioned in commit messages. 122 | - [`cpatterns`](git_vuln_finder/patterns/en/medium/c) is a set of standard vulnerability patterns see for C/C++-like languages. 123 | 124 | 125 | ## A sample partial output from Curl git repository 126 | 127 | ~~~bash 128 | $ git-vuln-finder -r ~/git/curl | jq . 129 | ... 130 | "6df916d751e72fc9a1febc07bb59c4ddd886c043": { 131 | "message": "loadlibrary: Only load system DLLs from the system directory\n\nInspiration provided by: Daniel Stenberg and Ray Satiro\n\nBug: https://curl.haxx.se/docs/adv_20160530.html\n\nRef: Windows DLL hijacking with curl, CVE-2016-4802\n", 132 | "language": "en", 133 | "commit-id": "6df916d751e72fc9a1febc07bb59c4ddd886c043", 134 | "summary": "loadlibrary: Only load system DLLs from the system directory", 135 | "stats": { 136 | "insertions": 180, 137 | "deletions": 8, 138 | "lines": 188, 139 | "files": 7 140 | }, 141 | "author": "Steve Holme", 142 | "author-email": "steve_holme@hotmail.com", 143 | "authored_date": 1464555460, 144 | "committed_date": 1464588867, 145 | "branches": [ 146 | "master" 147 | ], 148 | "pattern-selected": "(?i)(denial of service |\bXXE\b|remote code execution|\bopen redirect|OSVDB|\bvuln|\bCVE\b |\bXSS\b|\bReDoS\b|\bNVD\b|malicious|x−frame−options|attack|cross site |exploit|malicious|directory traversal |\bRCE\b|\bdos\b|\bXSRF \b|\bXSS\b|clickjack|session.fixation|hijack|\badvisory|\binsecure |security |\bcross−origin\b|unauthori[z|s]ed |infinite loop)", 149 | "pattern-matches": [ 150 | "hijack" 151 | ], 152 | "origin": "git@github.com:curl/curl.git", 153 | "origin-github-api": "https://api.github.com/repos/curl/curl/commits/6df916d751e72fc9a1febc07bb59c4ddd886c043", 154 | "tags": [], 155 | "cve": [ 156 | "CVE-2016-4802" 157 | ], 158 | "state": "cve-assigned" 159 | }, 160 | "c2b3f264cb5210f82bdc84a3b89250a611b68dd3": { 161 | "message": "CONNECT_ONLY: don't close connection on GSS 401/407 reponses\n\nPreviously, connections were closed immediately before the user had a\nchance to extract the socket when the proxy required Negotiate\nauthentication.\n\nThis regression was brought in with the security fix in commit\n79b9d5f1a42578f\n\nCloses #655\n", 162 | "language": "en", 163 | "commit-id": "c2b3f264cb5210f82bdc84a3b89250a611b68dd3", 164 | "summary": "CONNECT_ONLY: don't close connection on GSS 401/407 reponses", 165 | "stats": { 166 | "insertions": 4, 167 | "deletions": 2, 168 | "lines": 6, 169 | "files": 1 170 | }, 171 | "author": "Marcel Raad", 172 | "author-email": "raad@teamviewer.com", 173 | "authored_date": 1455523116, 174 | "committed_date": 1461704516, 175 | "branches": [ 176 | "master" 177 | ], 178 | "pattern-selected": "(?i)(denial of service |\bXXE\b|remote code execution|\bopen redirect|OSVDB|\bvuln|\bCVE\b |\bXSS\b|\bReDoS\b|\bNVD\b|malicious|x−frame−options|attack|cross site |exploit|malicious|directory traversal |\bRCE\b|\bdos\b|\bXSRF \b|\bXSS\b|clickjack|session.fixation|hijack|\badvisory|\binsecure |security |\bcross−origin\b|unauthori[z|s]ed |infinite loop)", 179 | "pattern-matches": [ 180 | "security " 181 | ], 182 | "origin": "git@github.com:curl/curl.git", 183 | "origin-github-api": "https://api.github.com/repos/curl/curl/commits/c2b3f264cb5210f82bdc84a3b89250a611b68dd3", 184 | "tags": [], 185 | "state": "under-review" 186 | }, 187 | ... 188 | ~~~ 189 | 190 | - Extracting CVE id(s) from git messages 191 | 192 | ~~~json 193 | "98d132cf6a879faf0147aa83ea0c07ff326260ed": { 194 | "message": "Add a macro for testing assertion in both debug and production builds\n\nIf we have an assert then in a debug build we want an abort() to occur.\nIn a production build we wan 195 | t the function to return an error.\n\nThis introduces a new macro to assist with that. The idea is to replace\nexisting use of OPENSSL_assert() with this new macro. The problem with\nOPENSSL 196 | _assert() is that it aborts() on an assertion failure in both debug\nand production builds. It should never be a library's decision to abort a\nprocess (we don't get to decide when to kill t 197 | he life support machine or\nthe nuclear reactor control system). Additionally if an attacker can\ncause a reachable assert to be hit then this can be a source of DoS attacks\ne.g. see CVE-20 198 | 17-3733, CVE-2015-0293, CVE-2011-4577 and CVE-2002-1568.\n\nReviewed-by: Tim Hudson \n(Merged from https://github.com/openssl/openssl/pull/3496)", 199 | "commit-id": "98d132cf6a879faf0147aa83ea0c07ff326260ed", 200 | "summary": "Add a macro for testing assertion in both debug and production builds", 201 | "stats": { 202 | "insertions": 18, 203 | "deletions": 0, 204 | "lines": 18, 205 | "files": 1 206 | }, 207 | "author": "Matt Caswell", 208 | "author-email": "matt@openssl.org", 209 | "authored_date": 1495182637, 210 | "committed_date": 1495457671, 211 | "branches": [ 212 | "master" 213 | ], 214 | "pattern-selected": "(?i)(denial of service |\bXXE\b|remote code execution|\bopen redirect|OSVDB|\bvuln|\bCVE\b |\bXSS\b|\bReDoS\b|\bNVD\b|malicious|x−frame−options|attack|cross site |ex 215 | ploit|malicious|directory traversal |\bRCE\b|\bdos\b|\bXSRF \b|\bXSS\b|clickjack|session.fixation|hijack|\badvisory|\binsecure |security |\bcross−origin\b|unauthori[z|s]ed |infinite loop)", 216 | "pattern-matches": [ 217 | "attack" 218 | ], 219 | "cve": [ 220 | "CVE-2017-3733", 221 | "CVE-2015-0293", 222 | "CVE-2011-4577", 223 | "CVE-2002-1568" 224 | ], 225 | "state": "cve-assigned" 226 | } 227 | ~~~ 228 | 229 | 230 | 231 | ## Usage for the special gharchive option 232 | 233 | ~~~bash 234 | $ git-vuln-finder -gh ../tests/gharchive_test.json 235 | ~~~ 236 | 237 | 238 | 239 | the value for the `gh` parameters need to be a json file, containing an array of each PushEvent you want to test. 240 | 241 | ~~~json 242 | [ 243 | { 244 | "id": "19351512310", 245 | "type": "PushEvent", 246 | "actor": { 247 | "id": 32466128, 248 | "login": "DavidCruciani", 249 | "display_login": "DavidCruciani", 250 | "gravatar_id": "", 251 | "url": "https://api.github.com/users/DavidCruciani", 252 | "avatar_url": "https://avatars.githubusercontent.com/u/32466128?" 253 | }, 254 | "repo": { 255 | "id": 424660123, 256 | "name": "ail-project/ail-feeder-gharchive", 257 | "url": "https://api.github.com/repos/ail-project/ail-feeder-gharchive" 258 | }, 259 | "payload": { 260 | "push_id": 8628652926, 261 | "size": 1, 262 | "distinct_size": 1, 263 | "ref": "refs/heads/main", 264 | "head": "910ed71a2819546a3f3bcce1ebb9e3984a8c8d86", 265 | "before": "40a9ef5dc6b2add5184a0a58401bfe9058faa8df", 266 | "commits": [ 267 | { 268 | "sha": "910ed71a2819546a3f3bcce1ebb9e3984a8c8d86", 269 | "author": { 270 | "email": "da.cruciani@laposte.net", 271 | "name": "David Cruciani" 272 | }, 273 | "message": "chg: [feeder] case sensitive", 274 | "distinct": true, 275 | "url": "https://api.github.com/repos/ail-project/ail-feeder-gharchive/commits/910ed71a2819546a3f3bcce1ebb9e3984a8c8d86" 276 | } 277 | ] 278 | }, 279 | "public": true, 280 | "created_at": "2021-12-15T16:06:43Z", 281 | "org": { 282 | "id": 62389074, 283 | "login": "ail-project", 284 | "gravatar_id": "", 285 | "url": "https://api.github.com/orgs/ail-project", 286 | "avatar_url": "https://avatars.githubusercontent.com/u/62389074?" 287 | } 288 | } 289 | ] 290 | ~~~ 291 | 292 | 293 | 294 | ## Usage for import 295 | 296 | If the goal is to import the module to use it, the method to call is `find_event` 297 | 298 | ~~~python 299 | from git_vuln_finder import find_event 300 | 301 | for element in event: 302 | for i in range(0,len(element["payload"]["commits"])): 303 | all_potential_vulnerabilities, all_cve_found, found = find_event(element["payload"]["commits"][i], element) 304 | ~~~ 305 | 306 | 307 | 308 | ## Output with gharchive option 309 | 310 | ~~~json 311 | { 312 | "repo_name": "LeandroFChaves/gerenciador-alunos", 313 | "message": "[UI] - Ajustes no css da aplica\u00e7\u00e3o\n\n- Adicionado padding para a exibi\u00e7\u00e3o do conte\u00fado das p\u00e1ginas;\n- Alinhado os bot\u00f5es de a\u00e7\u00f5es dos forms a direita da table", 314 | "language": "pt", 315 | "commit-id": "73a1c68b520853198eaac199a41d141ee96dc64d", 316 | "author": "LeandroFChaves", 317 | "author-email": "bbf3d4347c6affed0d9692115680849e2ace4d62@gmail.com", 318 | "authored_date": "2021-10-01T03:00:07Z", 319 | "branches": "refs/heads/master", 320 | "pattern-selected": "(?i)(denial of service|\\bXXE\\b|remote code execution|\\bopen redirect|OSVDB|\\bvuln|\\bCVE\\b|\\bXSS\\b|\\bReDoS\\b|\\bNVD\\b|malicious|x\u2212frame\u2212options|attack|cross site|exploit|malicious|directory traversal|\\bRCE\\b|\\bdos\\b|\\bXSRF \\b|\\bXSS\\b|clickjack|session.fixation|hijack|\\badvisory|\\binsecure|security|\\bcross\u2212origin\\b|unauthori[z|s]ed|infinite loop)", 321 | "pattern-matches": [ 322 | "dos" 323 | ], 324 | "origin-github-api": "https://api.github.com/repos/LeandroFChaves/gerenciador-alunos/commits/73a1c68b520853198eaac199a41d141ee96dc64d", 325 | "state": "under-review" 326 | } 327 | ~~~ 328 | 329 | 330 | 331 | 332 | 333 | # Running the tests 334 | 335 | ~~~bash 336 | $ pytest 337 | ~~~ 338 | 339 | # Warning 340 | 341 | git-vuln-finder is an automatic tool that detects potential vulnerabilities in commit messages based on specific keywords. If you intend to use the discovered vulnerability information, such as CVEs registration, please review the discovered vulnerabilities with the authors of the software before proceeding. 342 | 343 | # License and author(s) 344 | 345 | This software is free software and licensed under the AGPL version 3. 346 | 347 | - Copyright (c) 2019-2023 Alexandre Dulaunoy - https://github.com/adulau/ 348 | - Copyright (c) 2019-2023 All contributors to the project 349 | 350 | # Acknowledgment 351 | 352 | - Thanks to [Jean-Louis Huynen](https://github.com/gallypette) for the discussions about the crypto vulnerability patterns. 353 | - Thanks to [Sebastien Tricaud](https://github.com/stricaud) for the discussions regarding native language, commit messages and external patterns. 354 | - Thanks to [Cedric Bonhomme](https://github.com/cedricbonhomme) to make git-vuln-finder a Python library, add tests and improve the overall installation process. 355 | - Thanks to [David Cruciani](https://github.com/DavidCruciani) for the support of [gharchive](https://www.gharchive.org/). 356 | 357 | 358 | # Contributing 359 | 360 | We welcome contributions for the software and especially additional vulnerability patterns. Every contributors will be added in the [AUTHORS file](./AUTHORS) and 361 | collectively own this open source software. The contributors acknowledge the [Developer Certificate of Origin](https://developercertificate.org/). 362 | 363 | 364 | # References 365 | 366 | - [Notes](https://gist.github.com/adulau/dce5a6ca5c65017869bb01dfee576303#file-finding-vuln-git-commit-messages-md) 367 | - https://csce.ucmss.com/cr/books/2017/LFS/CSREA2017/ICA2077.pdf (mainly using CVE referenced in the commit message) - archive (http://archive.is/xep9o) 368 | - https://asankhaya.github.io/pdf/automated-identification-of-security-issues-from-commit-messages-and-bug-reports.pdf (2 main regexps) 369 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal 2 | -------------------------------------------------------------------------------- /bin/finder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Finding potential software vulnerabilities from git commit messages 5 | # 6 | # Software is free software released under the "GNU Affero General Public License v3.0" 7 | # 8 | # This software is part of cve-search.org 9 | # 10 | # Copyright (c) 2019-2020 Alexandre Dulaunoy - a@foo.be 11 | 12 | 13 | import json 14 | import sys 15 | import argparse 16 | 17 | from git_vuln_finder import find, find_event 18 | 19 | 20 | def main(): 21 | """Point of entry for the script. 22 | """ 23 | # Parsing arguments 24 | parser = argparse.ArgumentParser( 25 | description="Finding potential software vulnerabilities from git commit messages.", 26 | epilog="More info: https://github.com/cve-search/git-vuln-finder", 27 | ) 28 | parser.add_argument("-v", help="increase output verbosity", action="store_true") 29 | parser.add_argument("-r", type=str, help="git repository to analyse") 30 | parser.add_argument("-o", type=str, help="Output format: [json]", default="json") 31 | parser.add_argument( 32 | "-s", type=str, help="State of the commit found", default="under-review" 33 | ) 34 | parser.add_argument( 35 | "-p", 36 | type=str, 37 | help="Matching pattern to use: [vulnpatterns, cryptopatterns, cpatterns] - the pattern 'all' is used to match all the patterns at once.", 38 | default="vulnpatterns", 39 | ) 40 | parser.add_argument( 41 | "-c", 42 | help="output only a list of the CVE pattern found in commit messages (disable by default)", 43 | action="store_true", 44 | ) 45 | parser.add_argument( 46 | "-t", help="Include tags matching a specific commit", action="store_true" 47 | ) 48 | parser.add_argument( 49 | "-gh", help="special option for gharchive, pass a file containing a PushEvent in JSON format" 50 | ) 51 | args = parser.parse_args() 52 | 53 | if args.p not in ["vulnpatterns", "cryptopatterns", "cpatterns", "all"]: 54 | parser.print_usage() 55 | parser.exit() 56 | 57 | if not args.r and not args.gh: 58 | parser.print_usage() 59 | parser.exit() 60 | 61 | if args.gh: 62 | with open(args.gh, "r") as read_file: 63 | event = json.load(read_file) 64 | 65 | for element in event: 66 | for i in range(0,len(element["payload"]["commits"])): 67 | all_potential_vulnerabilities, all_cve_found, found = find_event(element["payload"]["commits"][i], element) 68 | 69 | else: 70 | # Launch the process 71 | all_potential_vulnerabilities, all_cve_found, found = find( 72 | args.r, 73 | tags_matching=args.t, 74 | commit_state=args.s, 75 | verbose=args.v, 76 | defaultpattern=args.p, 77 | ) 78 | 79 | # Output the result as json. Can be piped to another software. 80 | if not args.c: 81 | print(json.dumps(all_potential_vulnerabilities)) 82 | elif args.c: 83 | print(json.dumps(list(all_cve_found))) 84 | 85 | # Output the result to stderr. 86 | print( 87 | "{} CVE referenced found in commit(s)".format(len(list(all_cve_found))), 88 | file=sys.stderr, 89 | ) 90 | print( 91 | "Total potential vulnerability found in {} commit(s)".format(found), 92 | file=sys.stderr, 93 | ) 94 | -------------------------------------------------------------------------------- /doc/logos/git-vuln-finder-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cve-search/git-vuln-finder/bbd45b41cbb96d8246e5919667d80b0d8f5a289c/doc/logos/git-vuln-finder-small.png -------------------------------------------------------------------------------- /doc/logos/git-vuln-finder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cve-search/git-vuln-finder/bbd45b41cbb96d8246e5919667d80b0d8f5a289c/doc/logos/git-vuln-finder.png -------------------------------------------------------------------------------- /doc/logos/git-vuln-finder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 47 | 52 | 57 | git-vuln-finder 76 | -------------------------------------------------------------------------------- /git_vuln_finder/__init__.py: -------------------------------------------------------------------------------- 1 | from git_vuln_finder.pattern import build_pattern 2 | from git_vuln_finder.pattern import get_patterns 3 | from git_vuln_finder.vulnerability import find_vuln 4 | from git_vuln_finder.vulnerability import find_vuln_event 5 | from git_vuln_finder.vulnerability import summary 6 | from git_vuln_finder.vulnerability import summary_event 7 | from git_vuln_finder.vulnerability import extract_cve 8 | from git_vuln_finder.run import find 9 | from git_vuln_finder.run import find_event 10 | -------------------------------------------------------------------------------- /git_vuln_finder/pattern.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Finding potential software vulnerabilities from git commit messages 5 | # 6 | # Software is free software released under the "GNU Affero General Public License v3.0" 7 | # 8 | # This software is part of cve-search.org 9 | # 10 | # Copyright (c) 2019-2020 Alexandre Dulaunoy - a@foo.be 11 | 12 | 13 | import os 14 | import re 15 | from collections import defaultdict 16 | 17 | 18 | def tree(): 19 | """Autovivification. 20 | Call it a tree or call it 'patterns'. 21 | """ 22 | return defaultdict(tree) 23 | 24 | 25 | PATTERNS_PATH = os.path.dirname(os.path.abspath(__file__)) + "/patterns" 26 | 27 | 28 | def build_pattern(pattern_file): 29 | fp = open(pattern_file, "r") 30 | rex = "" 31 | try: 32 | prefix_fp = open(pattern_file + ".prefix", "r") 33 | rex += prefix_fp.read() 34 | prefix_fp.close() 35 | except: 36 | pass 37 | 38 | for line in fp.readlines(): 39 | rex += line.rstrip() + "|" 40 | rex = rex[:-1] # We remove the extra '| 41 | fp.close() 42 | 43 | try: 44 | suffix_fp = open(pattern_file + ".suffix", "r") 45 | rex += suffix_fp.read() 46 | suffix_fp.close() 47 | except: 48 | pass 49 | 50 | return rex 51 | 52 | 53 | def get_patterns(patterns_path=PATTERNS_PATH): 54 | patterns = tree() 55 | for root, dirs, files in os.walk(patterns_path): 56 | path = root.split(os.sep) 57 | for f in files: 58 | if f.endswith(".prefix") or f.endswith(".suffix"): 59 | continue 60 | npath = root[len(patterns_path) :].split(os.sep) 61 | try: 62 | npath.remove("") 63 | except ValueError: 64 | pass 65 | 66 | lang = npath[0] 67 | severity = npath[1] 68 | pattern_category = f 69 | 70 | rex = build_pattern(root + os.sep + f) 71 | patterns[lang][severity][pattern_category] = re.compile(rex) 72 | 73 | return patterns 74 | -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/c: -------------------------------------------------------------------------------- 1 | double[-| ]free 2 | buffer overflow 3 | double free 4 | race[-| ]condition 5 | -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/c.prefix: -------------------------------------------------------------------------------- 1 | (?i)( -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/c.suffix: -------------------------------------------------------------------------------- 1 | ) -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/crypto: -------------------------------------------------------------------------------- 1 | assessment 2 | lack of 3 | bad 4 | vulnerable 5 | missing 6 | unproper 7 | unsuitable 8 | breakable 9 | broken 10 | weak 11 | incorrect 12 | replace 13 | assessment 14 | pen([\s-]?)test 15 | pentest 16 | penetration([\s-]?)test 17 | report 18 | vulnerablity 19 | vulnerability 20 | replace 21 | fix 22 | issue 23 | fixes 24 | add 25 | remove 26 | check){s1,} 27 | (crypto 28 | cryptographic 29 | cryptography 30 | encipherement 31 | encryption 32 | ciphers 33 | cipher 34 | AES 35 | DES 36 | 3DES 37 | cipher 38 | GPG 39 | PGP 40 | OpenSSL 41 | SSH 42 | wireguard 43 | VPN 44 | CBC 45 | ECB 46 | CTR 47 | key[.|,|\s] 48 | private([\s-]?)key 49 | public([\s-]?)key size 50 | length 51 | strenght 52 | generation 53 | randomness 54 | entropy 55 | prng 56 | rng 57 | -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/crypto.prefix: -------------------------------------------------------------------------------- 1 | .*( -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/crypto.suffix: -------------------------------------------------------------------------------- 1 | ){1,} -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/vuln: -------------------------------------------------------------------------------- 1 | denial of service 2 | \bXXE\b 3 | remote code execution 4 | \bopen redirect 5 | OSVDB 6 | \bvuln 7 | \bCVE\b 8 | \bXSS\b 9 | \bReDoS\b 10 | \bNVD\b 11 | malicious 12 | x−frame−options 13 | attack 14 | cross site 15 | exploit 16 | malicious 17 | directory traversal 18 | \bRCE\b 19 | \bdos\b 20 | \bXSRF \b 21 | \bXSS\b 22 | clickjack 23 | session.fixation 24 | hijack 25 | \badvisory 26 | \binsecure 27 | security 28 | \bcross−origin\b 29 | unauthori[z|s]ed 30 | infinite loop -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/vuln.prefix: -------------------------------------------------------------------------------- 1 | (?i)( -------------------------------------------------------------------------------- /git_vuln_finder/patterns/en/medium/vuln.suffix: -------------------------------------------------------------------------------- 1 | ) -------------------------------------------------------------------------------- /git_vuln_finder/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Finding potential software vulnerabilities from git commit messages 5 | # 6 | # Software is free software released under the "GNU Affero General Public License v3.0" 7 | # 8 | # This software is part of cve-search.org 9 | # 10 | # Copyright (c) 2019-2020 Alexandre Dulaunoy - a@foo.be 11 | 12 | import sys 13 | import git 14 | import typing 15 | from git_vuln_finder import get_patterns, find_vuln, find_vuln_event, summary, summary_event 16 | 17 | 18 | def find( 19 | repo, 20 | tags_matching=False, 21 | commit_state="under-review", 22 | verbose=False, 23 | defaultpattern="all", 24 | ): 25 | # Initialization of the variables for the results 26 | repo = git.Repo(repo) 27 | found = 0 28 | all_potential_vulnerabilities = {} 29 | all_cve_found = set() 30 | 31 | # Initialization of the patterns 32 | patterns = get_patterns() 33 | vulnpatterns = patterns["en"]["medium"]["vuln"] 34 | cryptopatterns = patterns["en"]["medium"]["crypto"] 35 | cpatterns = patterns["en"]["medium"]["c"] 36 | 37 | if defaultpattern == "vulnpatterns": 38 | defaultpattern = vulnpatterns 39 | elif defaultpattern == "cryptopatterns": 40 | defaultpattern = cryptopatterns 41 | elif defaultpattern == "cpatterns": 42 | defaultpattern = cpatterns 43 | elif defaultpattern == "all": 44 | defaultpattern = [vulnpatterns, cryptopatterns, cpatterns] 45 | 46 | repo_heads = repo.heads 47 | repo_heads_names = [h.name for h in repo_heads] 48 | print(repo_heads_names, file=sys.stderr) 49 | origin = repo.remotes.origin.url 50 | tagmap = {} 51 | if tags_matching: 52 | for t in repo.tags: 53 | tagmap.setdefault(repo.commit(t).hexsha, []).append(str(t)) 54 | 55 | for branch in repo_heads_names: 56 | commits = list(repo.iter_commits(branch)) 57 | defaultpattern 58 | for commit in commits: 59 | if isinstance(defaultpattern, typing.Pattern): 60 | ret = find_vuln(commit, pattern=defaultpattern, verbose=verbose) 61 | if ret: 62 | rcommit = ret["commit"] 63 | _, potential_vulnerabilities, cve_found = summary( 64 | repo, 65 | rcommit, 66 | branch, 67 | tagmap, 68 | defaultpattern, 69 | origin=origin, 70 | vuln_match=ret["match"], 71 | tags_matching=tags_matching, 72 | commit_state=commit_state, 73 | ) 74 | all_potential_vulnerabilities.update(potential_vulnerabilities) 75 | all_cve_found.update(cve_found) 76 | found += 1 77 | elif isinstance(defaultpattern, list): 78 | for p in defaultpattern: 79 | ret = find_vuln(commit, pattern=p, verbose=verbose) 80 | if ret: 81 | rcommit = ret["commit"] 82 | _, potential_vulnerabilities, cve_found = summary( 83 | repo, 84 | rcommit, 85 | branch, 86 | tagmap, 87 | p, 88 | origin=origin, 89 | vuln_match=ret["match"], 90 | tags_matching=tags_matching, 91 | commit_state=commit_state, 92 | ) 93 | all_potential_vulnerabilities.update(potential_vulnerabilities) 94 | all_cve_found.update(cve_found) 95 | found += 1 96 | 97 | return all_potential_vulnerabilities, all_cve_found, found 98 | 99 | def find_event(commit, element): 100 | # Initialization of the variables for the results 101 | found = 0 102 | all_potential_vulnerabilities = {} 103 | all_cve_found = set() 104 | 105 | # Initialization of the patterns 106 | patterns = get_patterns() 107 | vulnpatterns = patterns["en"]["medium"]["vuln"] 108 | cryptopatterns = patterns["en"]["medium"]["crypto"] 109 | cpatterns = patterns["en"]["medium"]["c"] 110 | 111 | defaultpattern = [vulnpatterns, cryptopatterns, cpatterns] 112 | 113 | for p in defaultpattern: 114 | ret = find_vuln_event(commit["message"], pattern=p) 115 | if ret: 116 | potential_vulnerabilities, cve_found = summary_event( 117 | commit, 118 | p, 119 | element, 120 | vuln_match=ret["match"] 121 | ) 122 | all_potential_vulnerabilities.update(potential_vulnerabilities) 123 | all_cve_found.update(cve_found) 124 | found += 1 125 | 126 | return all_potential_vulnerabilities, all_cve_found, found 127 | -------------------------------------------------------------------------------- /git_vuln_finder/vulnerability.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Finding potential software vulnerabilities from git commit messages 5 | # 6 | # Software is free software released under the "GNU Affero General Public License v3.0" 7 | # 8 | # This software is part of cve-search.org 9 | # 10 | # Copyright (c) 2019-2020 Alexandre Dulaunoy - a@foo.be 11 | 12 | import re 13 | import sys 14 | from langdetect import detect as langdetect 15 | 16 | 17 | def find_vuln(commit, pattern, verbose=False): 18 | """Find a potential vulnerability from a commit message thanks to a regex 19 | pattern. 20 | """ 21 | m = pattern.search(commit.message) 22 | if m: 23 | if verbose: 24 | print("Match found: {}".format(m.group(0)), file=sys.stderr) 25 | print(commit.message, file=sys.stderr) 26 | print("---", file=sys.stderr) 27 | ret = {} 28 | ret["commit"] = commit 29 | ret["match"] = m.groups() 30 | return ret 31 | else: 32 | return None 33 | 34 | def find_vuln_event(commit_msg, pattern, verbose=False): 35 | """Find a potential vulnerability from a commit message thanks to a regex 36 | pattern. 37 | """ 38 | m = pattern.search(commit_msg) 39 | if m: 40 | if verbose: 41 | print("Match found: {}".format(m.group(0)), file=sys.stderr) 42 | print(commit_msg, file=sys.stderr) 43 | print("---", file=sys.stderr) 44 | ret = {} 45 | ret["commit"] = commit_msg 46 | ret["match"] = m.groups() 47 | return ret 48 | else: 49 | return None 50 | 51 | 52 | def summary( 53 | repo, 54 | commit, 55 | branch, 56 | tagmap, 57 | pattern, 58 | origin=None, 59 | vuln_match=None, 60 | tags_matching=False, 61 | commit_state="under-review", 62 | ): 63 | potential_vulnerabilities = {} 64 | rcommit = commit 65 | cve, cve_found = extract_cve(rcommit.message) 66 | if origin is not None: 67 | origin = origin 68 | if origin.find("github.com"): 69 | origin_github_api = origin.split(":")[1] 70 | (org_name, repo_name) = origin_github_api.split("/", 1) 71 | if repo_name.find(".git$"): 72 | repo_name = re.sub(r".git$", "", repo_name) 73 | origin_github_api = "https://api.github.com/repos/{}/{}/commits/{}".format( 74 | org_name, repo_name, rcommit.hexsha 75 | ) 76 | 77 | else: 78 | origin = "git origin unknown" 79 | # deduplication if similar commits on different branches 80 | if rcommit.hexsha in potential_vulnerabilities: 81 | potential_vulnerabilities[rcommit.hexsha]["branches"].append(branch) 82 | else: 83 | potential_vulnerabilities[rcommit.hexsha] = {} 84 | potential_vulnerabilities[rcommit.hexsha]["message"] = rcommit.message 85 | try: 86 | lang = langdetect(rcommit.message) 87 | except: 88 | lang = "unknown" 89 | potential_vulnerabilities[rcommit.hexsha]["language"] = lang 90 | potential_vulnerabilities[rcommit.hexsha]["commit-id"] = rcommit.hexsha 91 | potential_vulnerabilities[rcommit.hexsha]["summary"] = rcommit.summary 92 | potential_vulnerabilities[rcommit.hexsha]["stats"] = rcommit.stats.total 93 | potential_vulnerabilities[rcommit.hexsha]["author"] = rcommit.author.name 94 | potential_vulnerabilities[rcommit.hexsha]["author-email"] = rcommit.author.email 95 | potential_vulnerabilities[rcommit.hexsha][ 96 | "authored_date" 97 | ] = rcommit.authored_date 98 | potential_vulnerabilities[rcommit.hexsha][ 99 | "committed_date" 100 | ] = rcommit.committed_date 101 | potential_vulnerabilities[rcommit.hexsha]["branches"] = [] 102 | potential_vulnerabilities[rcommit.hexsha]["branches"].append(branch) 103 | potential_vulnerabilities[rcommit.hexsha]["pattern-selected"] = pattern.pattern 104 | potential_vulnerabilities[rcommit.hexsha]["pattern-matches"] = vuln_match 105 | potential_vulnerabilities[rcommit.hexsha]["origin"] = origin 106 | if origin_github_api: 107 | potential_vulnerabilities[commit.hexsha][ 108 | "origin-github-api" 109 | ] = origin_github_api 110 | potential_vulnerabilities[rcommit.hexsha]["tags"] = [] 111 | if tags_matching: 112 | if repo.commit(rcommit).hexsha in tagmap: 113 | potential_vulnerabilities[rcommit.hexsha]["tags"] = tagmap[ 114 | repo.commit(rcommit).hexsha 115 | ] 116 | if cve: 117 | potential_vulnerabilities[rcommit.hexsha]["cve"] = cve 118 | potential_vulnerabilities[rcommit.hexsha]["state"] = "cve-assigned" 119 | else: 120 | potential_vulnerabilities[rcommit.hexsha]["state"] = commit_state 121 | 122 | return rcommit.hexsha, potential_vulnerabilities, cve_found 123 | 124 | 125 | def summary_event( 126 | commit, 127 | pattern, 128 | element, 129 | vuln_match=None, 130 | commit_state="under-review" 131 | ): 132 | potential_vulnerabilities = {} 133 | 134 | cve, cve_found = extract_cve(commit["message"]) 135 | 136 | potential_vulnerabilities[commit["sha"]] = {} 137 | potential_vulnerabilities[commit["sha"]]["repo_name"] = element["repo"]["name"] 138 | potential_vulnerabilities[commit["sha"]]["message"] = commit["message"] 139 | potential_vulnerabilities[commit["sha"]]["language"] = langdetect(commit["message"]) 140 | potential_vulnerabilities[commit["sha"]]["commit-id"] = commit["sha"] 141 | potential_vulnerabilities[commit["sha"]]["author"] = commit["author"]["name"] 142 | potential_vulnerabilities[commit["sha"]]["author-email"] = commit["author"]["email"] 143 | potential_vulnerabilities[commit["sha"]]["authored_date"] = element["created_at"] 144 | potential_vulnerabilities[commit["sha"]]["branches"] = element["payload"]["ref"] 145 | potential_vulnerabilities[commit["sha"]]["pattern-selected"] = pattern.pattern 146 | potential_vulnerabilities[commit["sha"]]["pattern-matches"] = vuln_match 147 | potential_vulnerabilities[commit["sha"]]["origin-github-api"] = commit["url"] 148 | if cve: 149 | potential_vulnerabilities[commit["sha"]]["cve"] = cve 150 | potential_vulnerabilities[commit["sha"]]["state"] = "cve-assigned" 151 | else: 152 | potential_vulnerabilities[commit["sha"]]["state"] = commit_state 153 | 154 | return potential_vulnerabilities, cve_found 155 | 156 | 157 | def extract_cve(commit): 158 | cve_found = set() 159 | cve_find = re.compile(r"CVE-[1-2]\d{1,4}-\d{1,7}", re.IGNORECASE) 160 | m = cve_find.findall(commit) 161 | if m: 162 | for v in m: 163 | cve_found.add(v) 164 | return m, cve_found 165 | else: 166 | return None, set() 167 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "atomicwrites" 5 | version = "1.4.1" 6 | description = "Atomic file writes." 7 | optional = false 8 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 9 | files = [ 10 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 11 | ] 12 | 13 | [[package]] 14 | name = "attrs" 15 | version = "22.1.0" 16 | description = "Classes Without Boilerplate" 17 | optional = false 18 | python-versions = ">=3.5" 19 | files = [ 20 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, 21 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, 22 | ] 23 | 24 | [package.extras] 25 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] 26 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] 27 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] 28 | tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] 29 | 30 | [[package]] 31 | name = "certifi" 32 | version = "2023.7.22" 33 | description = "Python package for providing Mozilla's CA Bundle." 34 | optional = false 35 | python-versions = ">=3.6" 36 | files = [ 37 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 38 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 39 | ] 40 | 41 | [[package]] 42 | name = "charset-normalizer" 43 | version = "2.0.12" 44 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 45 | optional = false 46 | python-versions = ">=3.5.0" 47 | files = [ 48 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 49 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 50 | ] 51 | 52 | [package.extras] 53 | unicode-backport = ["unicodedata2"] 54 | 55 | [[package]] 56 | name = "colorama" 57 | version = "0.4.5" 58 | description = "Cross-platform colored terminal text." 59 | optional = false 60 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 61 | files = [ 62 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, 63 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, 64 | ] 65 | 66 | [[package]] 67 | name = "Deprecated" 68 | version = "1.2.13" 69 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 70 | optional = false 71 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 72 | files = [ 73 | {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, 74 | {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, 75 | ] 76 | 77 | [package.dependencies] 78 | wrapt = ">=1.10,<2" 79 | 80 | [package.extras] 81 | dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] 82 | 83 | [[package]] 84 | name = "flake8" 85 | version = "3.9.2" 86 | description = "the modular source code checker: pep8 pyflakes and co" 87 | optional = false 88 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 89 | files = [ 90 | {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, 91 | {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, 92 | ] 93 | 94 | [package.dependencies] 95 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 96 | mccabe = ">=0.6.0,<0.7.0" 97 | pycodestyle = ">=2.7.0,<2.8.0" 98 | pyflakes = ">=2.3.0,<2.4.0" 99 | 100 | [[package]] 101 | name = "gitdb" 102 | version = "4.0.9" 103 | description = "Git Object Database" 104 | optional = false 105 | python-versions = ">=3.6" 106 | files = [ 107 | {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, 108 | {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, 109 | ] 110 | 111 | [package.dependencies] 112 | smmap = ">=3.0.1,<6" 113 | 114 | [[package]] 115 | name = "GitPython" 116 | version = "3.1.18" 117 | description = "Python Git Library" 118 | optional = false 119 | python-versions = ">=3.6" 120 | files = [ 121 | {file = "GitPython-3.1.18-py3-none-any.whl", hash = "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8"}, 122 | {file = "GitPython-3.1.18.tar.gz", hash = "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b"}, 123 | ] 124 | 125 | [package.dependencies] 126 | gitdb = ">=4.0.1,<5" 127 | typing-extensions = {version = ">=3.7.4.0", markers = "python_version < \"3.8\""} 128 | 129 | [[package]] 130 | name = "idna" 131 | version = "3.4" 132 | description = "Internationalized Domain Names in Applications (IDNA)" 133 | optional = false 134 | python-versions = ">=3.5" 135 | files = [ 136 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 137 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 138 | ] 139 | 140 | [[package]] 141 | name = "importlib-metadata" 142 | version = "4.8.3" 143 | description = "Read metadata from Python packages" 144 | optional = false 145 | python-versions = ">=3.6" 146 | files = [ 147 | {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"}, 148 | {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"}, 149 | ] 150 | 151 | [package.dependencies] 152 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 153 | zipp = ">=0.5" 154 | 155 | [package.extras] 156 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 157 | perf = ["ipython"] 158 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] 159 | 160 | [[package]] 161 | name = "jsonschema" 162 | version = "3.2.0" 163 | description = "An implementation of JSON Schema validation for Python" 164 | optional = false 165 | python-versions = "*" 166 | files = [ 167 | {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, 168 | {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, 169 | ] 170 | 171 | [package.dependencies] 172 | attrs = ">=17.4.0" 173 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 174 | pyrsistent = ">=0.14.0" 175 | setuptools = "*" 176 | six = ">=1.11.0" 177 | 178 | [package.extras] 179 | format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] 180 | format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] 181 | 182 | [[package]] 183 | name = "langdetect" 184 | version = "1.0.9" 185 | description = "Language detection library ported from Google's language-detection." 186 | optional = false 187 | python-versions = "*" 188 | files = [ 189 | {file = "langdetect-1.0.9-py2-none-any.whl", hash = "sha256:7cbc0746252f19e76f77c0b1690aadf01963be835ef0cd4b56dddf2a8f1dfc2a"}, 190 | {file = "langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0"}, 191 | ] 192 | 193 | [package.dependencies] 194 | six = "*" 195 | 196 | [[package]] 197 | name = "mccabe" 198 | version = "0.6.1" 199 | description = "McCabe checker, plugin for flake8" 200 | optional = false 201 | python-versions = "*" 202 | files = [ 203 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 204 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 205 | ] 206 | 207 | [[package]] 208 | name = "more-itertools" 209 | version = "8.14.0" 210 | description = "More routines for operating on iterables, beyond itertools" 211 | optional = false 212 | python-versions = ">=3.5" 213 | files = [ 214 | {file = "more-itertools-8.14.0.tar.gz", hash = "sha256:c09443cd3d5438b8dafccd867a6bc1cb0894389e90cb53d227456b0b0bccb750"}, 215 | {file = "more_itertools-8.14.0-py3-none-any.whl", hash = "sha256:1bc4f91ee5b1b31ac7ceacc17c09befe6a40a503907baf9c839c229b5095cfd2"}, 216 | ] 217 | 218 | [[package]] 219 | name = "packaging" 220 | version = "21.3" 221 | description = "Core utilities for Python packages" 222 | optional = false 223 | python-versions = ">=3.6" 224 | files = [ 225 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 226 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 227 | ] 228 | 229 | [package.dependencies] 230 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 231 | 232 | [[package]] 233 | name = "pluggy" 234 | version = "0.13.1" 235 | description = "plugin and hook calling mechanisms for python" 236 | optional = false 237 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 238 | files = [ 239 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 240 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 241 | ] 242 | 243 | [package.dependencies] 244 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 245 | 246 | [package.extras] 247 | dev = ["pre-commit", "tox"] 248 | 249 | [[package]] 250 | name = "py" 251 | version = "1.11.0" 252 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 253 | optional = false 254 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 255 | files = [ 256 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 257 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 258 | ] 259 | 260 | [[package]] 261 | name = "pycodestyle" 262 | version = "2.7.0" 263 | description = "Python style guide checker" 264 | optional = false 265 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 266 | files = [ 267 | {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, 268 | {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, 269 | ] 270 | 271 | [[package]] 272 | name = "pyflakes" 273 | version = "2.3.1" 274 | description = "passive checker of Python programs" 275 | optional = false 276 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 277 | files = [ 278 | {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, 279 | {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, 280 | ] 281 | 282 | [[package]] 283 | name = "pymisp" 284 | version = "2.4.148.1" 285 | description = "Python API for MISP." 286 | optional = false 287 | python-versions = ">=3.6,<4.0" 288 | files = [ 289 | {file = "pymisp-2.4.148.1-py3-none-any.whl", hash = "sha256:af2da6ac61dd92b0b06cb968e3257c3926fb8a188c1d6123bdc054ac004d28c0"}, 290 | {file = "pymisp-2.4.148.1.tar.gz", hash = "sha256:20a34a7815bbf3f5bb75c72f290aa8c56694c3e3ed1e33b44d6b1b634980955b"}, 291 | ] 292 | 293 | [package.dependencies] 294 | deprecated = ">=1.2.12,<2.0.0" 295 | jsonschema = ">=3.2.0,<4.0.0" 296 | python-dateutil = ">=2.8.1,<3.0.0" 297 | requests = ">=2.25.1,<3.0.0" 298 | 299 | [package.extras] 300 | brotli = ["urllib3[brotli] (>=1.26.4,<2.0.0)"] 301 | docs = ["recommonmark (>=0.7.1,<0.8.0)", "sphinx-autodoc-typehints (>=1.12.0,<2.0.0)"] 302 | email = ["RTFDE (>=0.0.2,<0.0.3)", "extract_msg (>=0.28.7,<0.29.0)", "oletools (>=0.56.1,<0.57.0)"] 303 | fileobjects = ["lief (>=0.11.4,<0.12.0)", "pydeep (>=0.4,<0.5)", "python-magic (>=0.4.22,<0.5.0)"] 304 | openioc = ["beautifulsoup4 (>=4.9.3,<5.0.0)"] 305 | pdfexport = ["reportlab (>=3.5.67,<4.0.0)"] 306 | url = ["pyfaup (>=1.2,<2.0)"] 307 | virustotal = ["validators (>=0.18.2,<0.19.0)"] 308 | 309 | [[package]] 310 | name = "pyparsing" 311 | version = "3.0.7" 312 | description = "Python parsing module" 313 | optional = false 314 | python-versions = ">=3.6" 315 | files = [ 316 | {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, 317 | {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, 318 | ] 319 | 320 | [package.extras] 321 | diagrams = ["jinja2", "railroad-diagrams"] 322 | 323 | [[package]] 324 | name = "pyrsistent" 325 | version = "0.18.0" 326 | description = "Persistent/Functional/Immutable data structures" 327 | optional = false 328 | python-versions = ">=3.6" 329 | files = [ 330 | {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, 331 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, 332 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, 333 | {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, 334 | {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, 335 | {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, 336 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, 337 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, 338 | {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, 339 | {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, 340 | {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, 341 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, 342 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, 343 | {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, 344 | {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, 345 | {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, 346 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, 347 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, 348 | {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, 349 | {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, 350 | {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, 351 | ] 352 | 353 | [[package]] 354 | name = "pytest" 355 | version = "5.4.3" 356 | description = "pytest: simple powerful testing with Python" 357 | optional = false 358 | python-versions = ">=3.5" 359 | files = [ 360 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 361 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 362 | ] 363 | 364 | [package.dependencies] 365 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 366 | attrs = ">=17.4.0" 367 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 368 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 369 | more-itertools = ">=4.0.0" 370 | packaging = "*" 371 | pluggy = ">=0.12,<1.0" 372 | py = ">=1.5.0" 373 | wcwidth = "*" 374 | 375 | [package.extras] 376 | checkqa-mypy = ["mypy (==v0.761)"] 377 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 378 | 379 | [[package]] 380 | name = "python-dateutil" 381 | version = "2.8.2" 382 | description = "Extensions to the standard Python datetime module" 383 | optional = false 384 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 385 | files = [ 386 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 387 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 388 | ] 389 | 390 | [package.dependencies] 391 | six = ">=1.5" 392 | 393 | [[package]] 394 | name = "requests" 395 | version = "2.27.1" 396 | description = "Python HTTP for Humans." 397 | optional = false 398 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 399 | files = [ 400 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 401 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 402 | ] 403 | 404 | [package.dependencies] 405 | certifi = ">=2017.4.17" 406 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 407 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 408 | urllib3 = ">=1.21.1,<1.27" 409 | 410 | [package.extras] 411 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 412 | use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] 413 | 414 | [[package]] 415 | name = "setuptools" 416 | version = "59.6.0" 417 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 418 | optional = false 419 | python-versions = ">=3.6" 420 | files = [ 421 | {file = "setuptools-59.6.0-py3-none-any.whl", hash = "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e"}, 422 | {file = "setuptools-59.6.0.tar.gz", hash = "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373"}, 423 | ] 424 | 425 | [package.extras] 426 | docs = ["furo", "jaraco.packaging (>=8.2)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] 427 | testing = ["flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "paver", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-virtualenv (>=1.2.7)", "pytest-xdist", "sphinx", "virtualenv (>=13.0.0)", "wheel"] 428 | 429 | [[package]] 430 | name = "six" 431 | version = "1.16.0" 432 | description = "Python 2 and 3 compatibility utilities" 433 | optional = false 434 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 435 | files = [ 436 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 437 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 438 | ] 439 | 440 | [[package]] 441 | name = "smmap" 442 | version = "5.0.0" 443 | description = "A pure Python implementation of a sliding window memory map manager" 444 | optional = false 445 | python-versions = ">=3.6" 446 | files = [ 447 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 448 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 449 | ] 450 | 451 | [[package]] 452 | name = "typing-extensions" 453 | version = "4.1.1" 454 | description = "Backported and Experimental Type Hints for Python 3.6+" 455 | optional = false 456 | python-versions = ">=3.6" 457 | files = [ 458 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 459 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 460 | ] 461 | 462 | [[package]] 463 | name = "urllib3" 464 | version = "1.26.17" 465 | description = "HTTP library with thread-safe connection pooling, file post, and more." 466 | optional = false 467 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 468 | files = [ 469 | {file = "urllib3-1.26.17-py2.py3-none-any.whl", hash = "sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b"}, 470 | {file = "urllib3-1.26.17.tar.gz", hash = "sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21"}, 471 | ] 472 | 473 | [package.extras] 474 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 475 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 476 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 477 | 478 | [[package]] 479 | name = "wcwidth" 480 | version = "0.2.5" 481 | description = "Measures the displayed width of unicode strings in a terminal" 482 | optional = false 483 | python-versions = "*" 484 | files = [ 485 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 486 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 487 | ] 488 | 489 | [[package]] 490 | name = "wrapt" 491 | version = "1.14.1" 492 | description = "Module for decorators, wrappers and monkey patching." 493 | optional = false 494 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 495 | files = [ 496 | {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, 497 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, 498 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, 499 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, 500 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, 501 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, 502 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, 503 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, 504 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, 505 | {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, 506 | {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, 507 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, 508 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, 509 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, 510 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, 511 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, 512 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, 513 | {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, 514 | {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, 515 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, 516 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, 517 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, 518 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, 519 | {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, 520 | {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, 521 | {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, 522 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, 523 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, 524 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, 525 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, 526 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, 527 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, 528 | {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, 529 | {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, 530 | {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, 531 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, 532 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, 533 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, 534 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, 535 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, 536 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, 537 | {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, 538 | {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, 539 | {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, 540 | {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, 541 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, 542 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, 543 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, 544 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, 545 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, 546 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, 547 | {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, 548 | {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, 549 | {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, 550 | {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, 551 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, 552 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, 553 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, 554 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, 555 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, 556 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, 557 | {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, 558 | {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, 559 | {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, 560 | ] 561 | 562 | [[package]] 563 | name = "zipp" 564 | version = "3.6.0" 565 | description = "Backport of pathlib-compatible object wrapper for zip files" 566 | optional = false 567 | python-versions = ">=3.6" 568 | files = [ 569 | {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, 570 | {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, 571 | ] 572 | 573 | [package.extras] 574 | docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] 575 | testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] 576 | 577 | [metadata] 578 | lock-version = "2.0" 579 | python-versions = "^3.6" 580 | content-hash = "1d67a18fb0b33f1ad9b5b5e3a1096d83ef478de6232965aa5b47540cec19c510" 581 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "git-vuln-finder" 3 | version = "1.4" 4 | description = "Finding potential software vulnerabilities from git commit messages." 5 | authors = [ 6 | "Alexandre Dulaunoy " 7 | ] 8 | license = "AGPL-3.0-or-later" 9 | 10 | readme = "README.md" 11 | 12 | homepage = "https://github.com/cve-search/git-vuln-finder" 13 | repository = "https://github.com/cve-search/git-vuln-finder" 14 | documentation = "" 15 | 16 | keywords = [ 17 | "git", 18 | "cve", 19 | "scanner", 20 | "cve-search", 21 | "cve-scanning", 22 | "software-vulnerability", 23 | "software-vulnerabilities" 24 | ] 25 | 26 | classifiers = [ 27 | "Environment :: Console", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: Information Technology", 30 | "Intended Audience :: Science/Research", 31 | "Topic :: Security", 32 | "Operating System :: OS Independent", 33 | "Programming Language :: Python :: 3.6", 34 | "Programming Language :: Python :: 3.7", 35 | "Programming Language :: Python :: 3.8", 36 | "Programming Language :: Python :: 3.10", 37 | "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)" 38 | ] 39 | 40 | include = [ 41 | "AUTHORS", 42 | "COPYING", 43 | "bin/*" 44 | ] 45 | 46 | [tool.poetry.scripts] 47 | git-vuln-finder = "bin.finder:main" 48 | 49 | [tool.poetry.dependencies] 50 | python = "^3.6" 51 | langdetect = "^1.0.7" 52 | gitpython = "^3.0.5" 53 | pymisp = "^2.4.128" 54 | 55 | [tool.poetry.dev-dependencies] 56 | flake8 = "^3.7.9" 57 | pytest = "^5.3.2" 58 | 59 | [build-system] 60 | requires = ["poetry>=0.12"] 61 | build-backend = "poetry.masonry.api" 62 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import pytest 4 | 5 | from git import Repo 6 | 7 | 8 | @pytest.fixture(scope='session') 9 | def clone_curl(): 10 | """Clone the repository of curl for the tests.""" 11 | git_url = 'https://github.com/curl/curl.git' 12 | repo_dir = './test_repos/curl' 13 | repo = Repo.clone_from(url=git_url, to_path=repo_dir) 14 | #repo.heads['curl-7_67_0'].checkout() 15 | 16 | def teardown(): 17 | os.unlink(repo_dir) 18 | 19 | return repo_dir 20 | -------------------------------------------------------------------------------- /tests/gharchive_test.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "19351512310", 4 | "type": "PushEvent", 5 | "actor": { 6 | "id": 32466128, 7 | "login": "DavidCruciani", 8 | "display_login": "DavidCruciani", 9 | "gravatar_id": "", 10 | "url": "https://api.github.com/users/DavidCruciani", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/32466128?" 12 | }, 13 | "repo": { 14 | "id": 424660123, 15 | "name": "ail-project/ail-feeder-gharchive", 16 | "url": "https://api.github.com/repos/ail-project/ail-feeder-gharchive" 17 | }, 18 | "payload": { 19 | "push_id": 8628652926, 20 | "size": 1, 21 | "distinct_size": 1, 22 | "ref": "refs/heads/main", 23 | "head": "910ed71a2819546a3f3bcce1ebb9e3984a8c8d86", 24 | "before": "40a9ef5dc6b2add5184a0a58401bfe9058faa8df", 25 | "commits": [ 26 | { 27 | "sha": "910ed71a2819546a3f3bcce1ebb9e3984a8c8d86", 28 | "author": { 29 | "email": "da.cruciani@laposte.net", 30 | "name": "David Cruciani" 31 | }, 32 | "message": "chg: [feeder] case sensitive", 33 | "distinct": true, 34 | "url": "https://api.github.com/repos/ail-project/ail-feeder-gharchive/commits/910ed71a2819546a3f3bcce1ebb9e3984a8c8d86" 35 | } 36 | ] 37 | }, 38 | "public": true, 39 | "created_at": "2021-12-15T16:06:43Z", 40 | "org": { 41 | "id": 62389074, 42 | "login": "ail-project", 43 | "gravatar_id": "", 44 | "url": "https://api.github.com/orgs/ail-project", 45 | "avatar_url": "https://avatars.githubusercontent.com/u/62389074?" 46 | } 47 | } 48 | ] 49 | -------------------------------------------------------------------------------- /tests/sample_output.json: -------------------------------------------------------------------------------- 1 | { 2 | "ad0961874ee22098ecacab5f26942e3a23e38e11": { 3 | "message": "Merge pull request #1 from wllm-rbnt/csloop\n\nFix for infinite loop in Ciphers Suite decoding", 4 | "language": "en", 5 | "commit-id": "ad0961874ee22098ecacab5f26942e3a23e38e11", 6 | "summary": "Merge pull request #1 from wllm-rbnt/csloop", 7 | "stats": { 8 | "insertions": 7, 9 | "deletions": 1, 10 | "lines": 8, 11 | "files": 1 12 | }, 13 | "author": "Alexandre Dulaunoy", 14 | "author-email": "a@foo.be", 15 | "authored_date": 1427835868, 16 | "committed_date": 1427835868, 17 | "branches": [ 18 | "master" 19 | ], 20 | "pattern-selected": "(?i)(denial of service|\\bXXE\\b|remote code execution|\\bopen redirect|OSVDB|\\bvuln|\\bCVE\\b|\\bXSS\\b|\\bReDoS\\b|\\bNVD\\b|malicious|x−frame−options|attack|cross site|exploit|malicious|directory traversal|\\bRCE\\b|\\bdos\\b|\\bXSRF \\b|\\bXSS\\b|clickjack|session.fixation|hijack|\\badvisory|\\binsecure|security|\\bcross−origin\\b|unauthori[z|s]ed|infinite loop)", 21 | "pattern-matches": [ 22 | "infinite loop" 23 | ], 24 | "origin": "git@github.com:adulau/ssldump.git", 25 | "origin-github-api": "https://api.github.com/repos/adulau/ssldump/commits/ad0961874ee22098ecacab5f26942e3a23e38e11", 26 | "tags": [], 27 | "state": "under-review" 28 | }, 29 | "082cc7afed8a226a21427522a575d929ea2e98c3": { 30 | "message": "Fix for infinite loop in Ciphers Suite decoding\n", 31 | "language": "en", 32 | "commit-id": "082cc7afed8a226a21427522a575d929ea2e98c3", 33 | "summary": "Fix for infinite loop in Ciphers Suite decoding", 34 | "stats": { 35 | "insertions": 7, 36 | "deletions": 1, 37 | "lines": 8, 38 | "files": 1 39 | }, 40 | "author": "William Robinet", 41 | "author-email": "william.robinet@conostix.com", 42 | "authored_date": 1427291551, 43 | "committed_date": 1427291551, 44 | "branches": [ 45 | "master" 46 | ], 47 | "pattern-selected": "(?i)(denial of service|\\bXXE\\b|remote code execution|\\bopen redirect|OSVDB|\\bvuln|\\bCVE\\b|\\bXSS\\b|\\bReDoS\\b|\\bNVD\\b|malicious|x−frame−options|attack|cross site|exploit|malicious|directory traversal|\\bRCE\\b|\\bdos\\b|\\bXSRF \\b|\\bXSS\\b|clickjack|session.fixation|hijack|\\badvisory|\\binsecure|security|\\bcross−origin\\b|unauthori[z|s]ed|infinite loop)", 48 | "pattern-matches": [ 49 | "infinite loop" 50 | ], 51 | "origin": "git@github.com:adulau/ssldump.git", 52 | "origin-github-api": "https://api.github.com/repos/adulau/ssldump/commits/082cc7afed8a226a21427522a575d929ea2e98c3", 53 | "tags": [], 54 | "state": "under-review" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/test_finder.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from git_vuln_finder import find 4 | 5 | 6 | def test_find_vuln(clone_curl): 7 | all_potential_vulnerabilities, all_cve_found, found = find("./test_repos/curl/") 8 | 9 | #assert len(list(all_cve_found)) == 64 10 | assert "CVE-2018-1000122" in all_cve_found 11 | --------------------------------------------------------------------------------