├── LICENSE ├── README.md └── gh-vulns /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hexin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gh-vulns 2 | 3 | This displays all vulnerabilities in your own repositories. 4 | 5 | ![image](https://user-images.githubusercontent.com/10758173/120782092-9f99f280-c564-11eb-9443-90d8f2851adb.png) 6 | 7 | ## Requirements 8 | 9 | - [gh](https://github.com/cli/cli) 10 | - [jq](https://github.com/stedolan/jq) 11 | - [Zsh](https://zsh.sourceforge.io/) 12 | 13 | ## Installation 14 | 15 | ### gh v2 or later 16 | 17 | This subcommand can be installed with `gh extension`. See [gh extension](https://cli.github.com/manual/gh_extension). 18 | 19 | ```zsh 20 | gh extension install hexium310/gh-vulns 21 | ``` 22 | 23 | ### gh v1 24 | 25 | With zinit: 26 | 27 | ```zsh 28 | zinit ice lucid as'program' 29 | zinit light hexium310/gh-vulns 30 | ``` 31 | 32 | Or install this repository on your `$PATH`. 33 | 34 | ## Usage 35 | 36 | You can pass a GitHub username that have repositories you want to get vulnerabilities or set it to `$GH_VULNS_USERNAME`. 37 | When a username isn't passed and `$GH_VULNS_USERNAME` isn't set, the one got using GitHub API is used. 38 | 39 | ``` 40 | gh vulns hexium310 41 | 42 | # Using $GH_VULNS_USERNAME or GitHub API 43 | gh vulns 44 | ``` 45 | 46 | Besides, you can create a alias for `gh` if you use gh v1. 47 | 48 | ``` 49 | gh alias set --shell vulns 'gh-vulns $@' 50 | ``` 51 | -------------------------------------------------------------------------------- /gh-vulns: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | autoload -Uz colors 4 | colors 5 | 6 | gh-vulns() { 7 | local owner=${1:-${GH_VULNS_USERNAME:-$(gh api graphql --jq '.data.viewer.login' -f query='query { viewer { login } }')}} 8 | local current_ghsa_id endCursor result vulnerability 9 | local repositories="" 10 | 11 | while 12 | result=$(gh api graphql \ 13 | --jq '.data | select(.repositoryOwner != null).repositoryOwner.repositories | { pageInfo: .pageInfo, repositories: [(.edges[] | select(.node.vulnerabilityAlerts.nodes | length > 0)).node | { repository: .name, url: .url, vulnerabilityAlerts: .vulnerabilityAlerts.nodes[] }] | group_by(.vulnerabilityAlerts.securityVulnerability.advisory.ghsaId) }' \ 14 | -F owner=$owner -F cursor=${endCursor:-null} -f query='query ($owner: String!, $cursor: String) { 15 | repositoryOwner(login: $owner) { 16 | repositories(first: 100, after: $cursor) { 17 | pageInfo { 18 | startCursor 19 | endCursor 20 | hasNextPage 21 | hasPreviousPage 22 | } 23 | edges { 24 | node { 25 | name 26 | url 27 | vulnerabilityAlerts(first: 100) { 28 | nodes { 29 | vulnerableRequirements 30 | vulnerableManifestPath 31 | securityVulnerability { 32 | advisory { 33 | ghsaId 34 | publishedAt 35 | notificationsPermalink 36 | summary 37 | } 38 | severity 39 | vulnerableVersionRange 40 | firstPatchedVersion { 41 | identifier 42 | } 43 | package { 44 | name 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | }') 54 | 55 | if [[ -z $result ]]; then 56 | return 57 | fi 58 | 59 | repositories+=$(jq 'select(.repositories != null) | { repositories: .repositories[] | values }' <<< $result) 60 | 61 | endCursor=$(jq -r '.pageInfo.endCursor | strings' <<< $result) 62 | $(jq -r '.pageInfo.hasNextPage' <<< $result) 63 | do :; done 64 | 65 | repositories=$(jq --slurp ' 66 | reduce .[] as $item ([]; . + $item.repositories) 67 | | [group_by(.vulnerabilityAlerts.securityVulnerability.advisory.ghsaId)[] | flatten] 68 | ' <<< $repositories) 69 | local vulnerabilities=($(jq ' 70 | walk(if type == "object" then to_entries else . end) 71 | | walk(if type == "object" then if .key == "repository" then "", .value else .value end else . end) 72 | | flatten | .[] 73 | ' <<< $repositories)) 74 | vulnerabilities=(${(ps:"":)vulnerabilities}) 75 | 76 | local keys=( 77 | repository 78 | url 79 | ghsa_id 80 | notifications_permalink 81 | published_at 82 | summary 83 | first_patched_version 84 | package_name 85 | severity 86 | vulnerable_version_range 87 | vulnerable_manifest_path 88 | vulnerable_requirements 89 | ) 90 | local -A colored_severities=( 91 | CRITICAL "${bg[red]}${fg_bold[black]} CRITICAL $reset_color" 92 | HIGH "\x1b[48;2;255;165;0m${fg_bold[black]} HIGH $reset_color" 93 | MODERATE "${bg[yellow]}${fg_bold[black]} MODERATE $reset_color" 94 | LOW "${bg[blue]}${fg_bold[black]} LOW $reset_color" 95 | ) 96 | for vulnerability in $vulnerabilities; do 97 | local tmp=(${(Q)${(z)vulnerability}}) 98 | local -A info=(${keys:^tmp}) 99 | 100 | if [[ ${info[ghsa_id]} != $current_ghsa_id ]]; then 101 | if [[ -n $current_ghsa_id ]]; then 102 | echo 103 | fi 104 | current_ghsa_id=${info[ghsa_id]} 105 | echo ${bg[green]}${fg_bold[black]} ${info[package_name]} $reset_color 106 | echo "Upgrade to version \e[4m${fg_bold[default]}${info[first_patched_version]}${fg_no_bold[default]} or later$reset_color" 107 | echo Details: 108 | echo -n " ${info[ghsa_id]} ${colored_severities[${info[severity]}]}" 109 | echo -n " published at \e[4m" 110 | case $OSTYPE in 111 | darwin*) 112 | date -j -f '%Y-%m-%dT%H:%M:%SZ' "${info[published_at]}" +"%d/%m/%Y %H:%M:%S$reset_color" 113 | ;; 114 | linux*) 115 | date -d "$(sed -r 's/T([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})Z/ \1:\2:\3/' <<< ${info[published_at]})" +"%d/%m/%Y %H:%M:%S$reset_color" 116 | ;; 117 | *) 118 | echo "${info[published_at]}$reset_color" 119 | ;; 120 | esac 121 | 122 | echo " ${fg[blue]}\e[4m${info[notifications_permalink]}$reset_color" 123 | echo " Vulnerable versions: ${info[vulnerable_version_range]}" 124 | echo " \e[3mSummary" 125 | echo " ${info[summary]}$reset_color" 126 | echo 127 | echo "Target repositor$((( ${(M)#${(z)vulnerabilities}:#${(qqq)current_ghsa_id}} >= 2 )) && echo 'ies' || echo 'y'): " 128 | fi 129 | echo " ${bg[grey]}${fg_bold[default]}$owner/${info[repository]}$reset_color (${fg[blue]}\e[4m${info[url]}$reset_color)" 130 | echo " ${info[vulnerable_requirements]} in ${info[vulnerable_manifest_path]}" 131 | echo 132 | done 133 | } 134 | 135 | gh-vulns $1 136 | --------------------------------------------------------------------------------