├── .github └── workflows │ ├── build_and_test.yml │ ├── build_executables.yml │ ├── manual.yml │ └── manual_test_repo.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── action.yml ├── dist ├── fsevents.node ├── index.js ├── index.js.map ├── sourcemap-register.js └── templates │ ├── executive_summary.html │ ├── summary.html │ ├── summary_old.html │ └── vulnerability.html ├── package-lock.json ├── package.json ├── samples ├── __old_to_remove │ ├── cplusplus_scan.json │ ├── cpp_scan.json │ ├── java_result.json │ ├── javascript_scan.json │ ├── report.json │ ├── sample_data.json │ ├── summary │ │ └── small.json │ └── vulnerabilities.json ├── reportJson │ ├── octodemo │ │ └── ghas-reporting │ │ │ ├── payload.json │ │ │ └── summary.html │ └── peter-murray │ │ └── advanced-security-java │ │ └── payload.json └── sarif │ ├── java │ ├── basic │ │ └── java.sarif │ └── detailed │ │ └── java.sarif │ └── peter-murray │ └── advanced-security-java │ ├── java-builtin.sarif │ └── javascript-builtin.sarif ├── src ├── DataCollector.ts ├── ReportGenerator.test.ts ├── ReportGenerator.ts ├── codeScanning │ ├── CodeScanningAlert.ts │ ├── CodeScanningResults.ts │ ├── GitHubCodeScanning.test.ts │ └── GitHubCodeScanning.ts ├── dependencies │ ├── Dependency.ts │ ├── DependencySet.ts │ ├── DependencyTypes.ts │ ├── GitHubDependencies.test.ts │ ├── GitHubDependencies.ts │ └── Vulnerability.ts ├── executable.ts ├── index.ts ├── pdf │ ├── pdfWriter.test.ts │ └── pdfWriter.ts ├── sarif │ ├── CodeScanningResult.ts │ ├── CodeScanningRule.ts │ ├── SarifDataTypes.ts │ ├── SarifReport.ts │ └── SarifReportFinder.ts ├── templating │ ├── ReportData.ts │ ├── ReportTypes.ts │ ├── Template.test.ts │ └── Template.ts └── testUtils.ts ├── summary_report_example.png ├── templates ├── executive_summary.html ├── summary.html ├── summary_old.html └── vulnerability.html └── tsconfig.json /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout Sources 13 | uses: actions/checkout@v2 14 | 15 | - name: Install Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 12 19 | 20 | - name: Install Dependencies 21 | run: npm ci 22 | 23 | - name: Build 24 | run: npm run build 25 | 26 | - name: Test 27 | env: 28 | GH_TOKEN: ${{ secrets.SECURITY_TOKEN }} 29 | run: npm test 30 | -------------------------------------------------------------------------------- /.github/workflows/build_executables.yml: -------------------------------------------------------------------------------- 1 | name: Build Bundle Executables 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | strategy: 9 | matrix: 10 | include: 11 | - type: linux-x64 12 | - type: mac-x64 13 | - type: windows-x64 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Sources 19 | uses: actions/checkout@v2 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: 12 25 | 26 | - name: Install Dependencies 27 | run: npm ci 28 | 29 | - name: Build Executable 30 | run: npm run build-exe-${{ matrix.type }} 31 | 32 | - name: npm pack 33 | run: npm pack 34 | 35 | - name: Attach artifact 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: github-security-report-bundle-${{ matrix.type }} 39 | path: github-security-report-bundle.zip 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | name: Manual Test 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | 12 | - name: Checkout Sources 13 | uses: actions/checkout@v2 14 | 15 | - name: Invoke Action 16 | uses: ./ 17 | with: 18 | token: ${{ secrets.SECURITY_TOKEN }} 19 | sarifReportDir: ./samples/sarif/peter-murray/advanced-security-java 20 | outputDir: . 21 | 22 | - name: Upload Artifacts 23 | uses: actions/upload-artifact@v2 24 | with: 25 | name: reports 26 | path: ./*.pdf 27 | -------------------------------------------------------------------------------- /.github/workflows/manual_test_repo.yml: -------------------------------------------------------------------------------- 1 | name: Manual Test Repository 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | 12 | - name: Checkout Sources 13 | uses: actions/checkout@v2 14 | 15 | - name: Invoke Action 16 | uses: ./ 17 | with: 18 | token: ${{ secrets.TEST_TOKEN }} 19 | sarifReportDir: ./samples/sarif/java/detailed 20 | outputDir: . 21 | repository: octodemo/ghas-reporting 22 | 23 | - name: Upload Artifacts 24 | uses: actions/upload-artifact@v2 25 | with: 26 | name: reports 27 | path: ./*.pdf 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.tgz 3 | 4 | .DS_Store 5 | 6 | # Intermeidate directory to TS to JS 7 | lib 8 | 9 | # IntelliJ IDEA 10 | .idea 11 | 12 | # Binary executables 13 | github-security-report* 14 | .local-chromium/ 15 | runtime/ 16 | tmp/ 17 | *.zip 18 | 19 | # Output directory for tests generating reports or other temp data 20 | _tmp/ 21 | 22 | # Any PDF files generated from testing 23 | *.pdf -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .local-chromium 2 | .github/ 3 | _tmp/ 4 | lib/ 5 | dist/ 6 | samples/ 7 | src/ 8 | templates/ 9 | package.json 10 | summary_report_example.png 11 | *.tgz 12 | *.zip 13 | tsconfig.json 14 | .idea 15 | action.yml 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug.onTaskErrors": "showErrors", 3 | "files.trimTrailingWhitespace": true, 4 | "editor.tabSize": 2, 5 | "editor.insertSpaces": true, 6 | "editor.detectIndentation": false, 7 | "[javascript]": { 8 | "editor.tabSize": 2 9 | }, 10 | "[json]": { 11 | "editor.tabSize": 2 12 | }, 13 | "[yaml]": { 14 | "editor.tabSize": 2 15 | }, 16 | "[html]": { 17 | "editor.tabSize": 2 18 | }, 19 | // GitHub Codespace Theme 20 | "workbench.colorTheme": "GitHub Dark Dimmed" 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Peter Murray] 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 | # github-security-report-action 2 | 3 | A GitHub Action for generating PDF reports for GitHub Advanced Security Code Scan Results and Dependency Vulnerabilities. 4 | 5 | The action comes with some predefined HTML templates using [Nunjucks](https://mozilla.github.io/nunjucks/templating.html), 6 | along with the ability to in the future provide your own templates to the renderer. 7 | 8 | Due to the nature of CodeQL Analysis this action ideally should be executed after the `github/codeql-action/analyze` 9 | action step, as this will generate the SARIF files on the runner which can be used to identify ALL the rules that were 10 | applied during the analysis. The results stored on your repository will only contain the results that generated an alert. 11 | 12 | ## Processing 13 | 14 | The action will use the provided token to load all the dependencies, dependency vulnerabilities and the Code Scanning 15 | results for the specified repository. It will then look in the directory specified for any SARIF reports. 16 | 17 | With this data it will construct a JSON payload that it then passes into the template system (using Nunjucks a Jinja 18 | like templating system for JavaScript) and will generate a Summary Report (with more of these to come in the future) 19 | providing a roll up summary security report in HTML. 20 | 21 | Using this HTML, it then passes it over to Puppeteer to render this in a headless Chromium before generating a PDF and 22 | saving it in the specified directory. 23 | 24 | ## Parameters 25 | 26 | * `token`: A GitHub Personal Access Token with access to `repo` scope 27 | * `sarifReportDir`: The directory to look for SARIF reports (from the CodeQL analyze action this defaults to `../results`) 28 | * `outputDir`: The output directory for the PDF reports, defaults to `github.workspace` 29 | * `repository`: The repository in `/` form, defaults to `github.repository` 30 | 31 | 32 | ## Templates 33 | 34 | Currently the templates are hard coded into the action. There are extension points built into the action that will allow 35 | a future release to provide customization of these templates, via an ability to specify your own. 36 | 37 | 38 | ## Examples 39 | 40 | ``` 41 | name: Generate Security Report 42 | uses: peter-murray/github-security-report-action@v2 43 | with: 44 | token: ${{ secrets.SECURITY_TOKEN }} 45 | ``` 46 | 47 | Example summary report output: 48 | ![Example summary report](summary_report_example.png) 49 | 50 | 51 | ## Standalone execution 52 | 53 | For the v2 version, there are bundles that can be used to provide a command line client executable for Linux, MacOS and Windows platforms. The bundles can be downloaded from the [v2 Release](https://github.com/peter-murray/github-security-report-action/releases/tag/v2) assets. 54 | 55 | * [Linux bundle](https://github.com/peter-murray/github-security-report-action/releases/download/v2/github-security-report-bundle-linux-x64.zip) 56 | * [MacOS bundle](https://github.com/peter-murray/github-security-report-action/releases/download/v2/github-security-report-bundle-mac-x64.zip) 57 | * [Windows bundle](https://github.com/peter-murray/github-security-report-action/releases/download/v2/github-security-report-bundle-windows-x64.zip) 58 | 59 | ### Installation 60 | Just download and extract the zip bundle for your target platform. Inside there is a file starting with `github-security-report` with a target platform suffix or .exe extension in the case of Windows. 61 | 62 | ### Running 63 | Just call the platform executable and pass in the arguments as required. The arguments are the same as that of the GitHub Action, and you can get the full details from invoking the `--help` option on the executable as it will output detailed help 64 | 65 | Options: 66 | * `-t`, `--token`: The GitHub Personal Access Token that has the necessary access for security and dependency API endpoints. 67 | * `-r`, `--repository`: The repository that contains the source code, in `/` form, e.g. `peter-murray/node-hue-api` 68 | * `-s`, `--sarif-directory`: The directory containing the SARIF report files 69 | * `-o`, `--output-directory`: The directory to output the PDF report to. This will be created if it does not exist. 70 | 71 | An example of running the MacOS command line executable from the un: 72 | ``` 73 | $ ./github-security-report-mac-x64 -t -r peter-murray/node-hue-api -s 74 | ``` 75 | The above command would output a `summary.pdf` file in the current working directory. 76 | 77 | ## Future improvements 78 | 79 | * Add support for selecting reporting templates to the parameters 80 | * Example of extending html templates and using them 81 | 82 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Security Report Action 2 | description: Generates security reports for a GitHub repository 3 | author: Peter Murray 4 | 5 | inputs: 6 | token: 7 | description: GitHub Access Token with permissions for Dependencies and Security API access on the repository. 8 | required: true 9 | 10 | sarifReportDir: 11 | description: The CodeQL output directory for SARIF report(s). 12 | required: true 13 | default: "../results" 14 | 15 | outputDir: 16 | description: The output directory for the generated report(s). 17 | required: true 18 | default: ${{ github.workspace }} 19 | 20 | repository: 21 | description: Repository name with owner. For example, peter-murray/github-security-report 22 | required: true 23 | default: ${{ github.repository }} 24 | 25 | runs: 26 | using: node12 27 | main: dist/index.js 28 | 29 | branding: 30 | icon: shield 31 | color: green -------------------------------------------------------------------------------- /dist/fsevents.node: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter-murray/github-security-report-action/42aa5945bd5b688f14868891df0ecae0024b3c60/dist/fsevents.node -------------------------------------------------------------------------------- /dist/templates/executive_summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security - Executive Summary 6 | 7 | 126 | 127 | 128 | 129 | 130 |
131 |
132 | 133 |
134 |
GitHub Advanced Security - Executive Summary
135 |
Prepared Sep 10, 2020
136 |
137 |
138 | 139 |
140 |

Executive Summary

141 |
142 |
143 | 144 |
145 |
Repository: octodemo/test-repo
146 |
147 | 148 |
149 | 150 |
151 | 152 |
153 |
Code Scanning Status
154 |
155 |
156 | Open: 0 157 |
158 |
159 |
160 | 161 |
162 | 163 |
164 | 165 |
166 |
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /dist/templates/summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security Report 6 | 7 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 |
45 |
46 |
47 | 50 |

GitHub Advanced Security
Summary Report

51 |
52 |
53 | 54 |
55 |
GitHub Repository:
56 |
{{ github.owner }}/{{ github.repo }}
57 | 58 |
Generated:
59 |
{{ metadata.created }}
60 | 61 |
62 | 63 | {% if sca %} 64 |
65 |
66 |
67 |

Software Composition Analysis

68 |
69 |
70 | 71 |
72 |
73 |
74 |
75 |
Dependencies
76 |
{{ sca.dependencies.totalDependencies }}
77 |
78 | 79 |
80 | 83 | 87 |
88 |
89 |
90 | 91 | {% if sca.vulnerabilities %} 92 |
93 |
94 |
95 |
Dependency Vulnerabilities
96 |
{{ sca.vulnerabilities.total }}
97 |
98 | 99 |
100 | 103 | 106 | 109 | 112 |
113 |
114 |
115 | {% endif %} 116 |
117 |
118 | {% endif %} 119 | 120 | {% if scanning %} 121 |
122 |
123 |
124 |

Code Scanning

125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 |
Code Scanning Rules Applied
133 |
{{ scanning.rules | length }}
134 |
135 |
136 |
137 | 138 |
139 |
140 | 141 |
142 |
143 |
144 |
145 |
CWE Coverage
146 |
{{ scanning.cwe.cwes | length }}
147 |
148 |
149 |
150 |
    151 | {% for cwe in scanning.cwe.cwes | sort %} 152 |
  • {{ cwe }}
  • 153 | {% endfor %} 154 |
155 |
156 |
157 |
158 |
159 |
160 | 161 |
162 |
163 | {% if scanning.results.open %} 164 |
165 |
166 |
Open Findings
167 |
{{ scanning.results.open.total }}
168 |
169 |
170 | {% if scanning.results.open.total > 0 %} 171 | 174 | 177 | {% else %} 178 | 179 | {% endif %} 180 |
181 |
182 | {% endif %} 183 |
184 | 185 |
186 | {% if scanning.results.closed %} 187 |
188 |
189 |
Closed Findings
190 |
{{ scanning.results.closed.total }}
191 |
192 |
193 | {% if scanning.results.closed.total > 0 %} 194 | 197 | 200 | {% else %} 201 | 202 | {% endif %} 203 |
204 |
205 | {% endif %} 206 |
207 |
208 |
209 | {% endif %} 210 |
211 | 212 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /dist/templates/summary_old.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security Summary 6 | 7 | 8 | 9 |

GitHub Advanced Security Summary

10 | 11 |

Software Composition Analysis (SCA)

12 | 13 |

Number of Dependencies: {{dependencies.deps.totalDependencies}}

14 | 15 |

SCA Dependency Vulnerability Summary:

16 |

17 |

    18 |
  • Critical: {{dependencies.vulnerabilities.CRITICAL.length}}
  • 19 |
  • High: {{dependencies.vulnerabilities.HIGH.length}}
  • 20 |
  • Moderate: {{dependencies.vulnerabilities.MODERATE.length}}
  • 21 |
  • Low: {{dependencies.vulnerabilities.LOW.length}}
  • 22 |
23 |

24 | 25 |

Code Scaning

26 |

27 | Total CWE Vulnerabilities Checked: {{codeScanning.cwes.length}} 28 | 29 |

30 | {{#each codeScanning.cwes}} 31 | {{this}} 32 | {{/each}} 33 |
34 | 35 |

36 | 37 |

Open Violations

38 |
    39 |
  • Errors: {{codeScanning.open.error.length}}
  • 40 |
  • Warnings: {{codeScanning.open.warning.length}}
  • 41 |
42 | 43 |

Errors

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {{#each codeScanning.open.error}} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {{/each}} 63 |
ToolNameCreatedTagsCWELink
{{this.tool}}{{this.name}}{{this.created}}{{this.rule.details.tags}}{{this.rule.details.cwes}}{{this.url}}
64 | 65 |

Warnings

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {{#each codeScanning.open.warning}} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {{/each}} 85 |
ToolNameCreatedTagsCWELink
{{this.tool}}{{this.name}}{{this.created}}{{this.rule.tags}}{{this.rule.cwes}}{{this.url}}
86 | 87 |

Dependencies Detail

88 | 89 |

Vulnerabilities

90 | 91 | {{#if dependencies.vulnerabilities.CRITICAL.length}} 92 |

Critical

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | {{#each dependencies.vulnerabilities.CRITICAL}} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | {{/each}} 114 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
115 | {{/if}} 116 | 117 | {{#if dependencies.vulnerabilities.HIGH.length}} 118 |

High

119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | {{#each dependencies.vulnerabilities.HIGH}} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | {{/each}} 140 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
141 | {{/if}} 142 | 143 | {{#if dependencies.vulnerabilities.LOW.length}} 144 |

Low

145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | {{#each dependencies.vulnerabilities.LOW}} 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | {{/each}} 166 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
167 | {{/if}} 168 | 169 | {{#if dependencies.vulnerabilities.MODERATE.length}} 170 |

Moderate

171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | {{#each dependencies.vulnerabilities.MODERATE}} 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | {{/each}} 192 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
193 | {{/if}} 194 | 195 |

Detected Dependencies

196 |

197 | Maven Dependencies: 198 | 199 | 200 | {{#each dependencies.deps.dependencies.maven}} 201 | 202 | 203 | 204 | 205 | {{/each}} 206 |
{{this.name}}{{this.version}}
207 |

208 |

209 | NPM Dependencies: 210 | 211 | 212 | {{#each dependencies.deps.dependencies.npm}} 213 | 214 | 215 | 216 | 217 | {{/each}} 218 |
{{this.name}}{{this.version}}
219 |

220 | 221 | 222 | -------------------------------------------------------------------------------- /dist/templates/vulnerability.html: -------------------------------------------------------------------------------- 1 | {% for severity, vulnerabilities in sca.vulnerabilities %} 2 |
3 |

{{ severity }}

4 |
    5 | {% for vuln in vulnerabilities %} 6 |
  • 7 |
    8 |
    {{ vuln.advisory.summary }}
    9 |
    {{ vuln.created }}
    10 |
    {{ vuln.published }}
    11 |
    12 | {% for identifier in vuln.advisory.identifiers %} 13 |
    {{ identifier.value }}
    14 | {% endfor %} 15 |
    {{ vuln.advisory.permalink }}
    16 |
    {{ vuln.advisory.description }}
    17 |
    18 |
    19 |
  • 20 | {% endfor %} 21 |
22 |
23 | {% endfor %} 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-security-report", 3 | "version": "2.0.0", 4 | "description": "Generates a report from GitHub CodeQL and Dependency information", 5 | "scripts": { 6 | "build": "tsc", 7 | "pre-build-exe": "npm run build && ncc build lib/executable.js -o runtime", 8 | "build-exe-linux-x64": "npm run pre-build-exe && nexe -i runtime/index.js -t linux-x64-12.16.2 -o github-security-report-linux-x64", 9 | "build-exe-mac-x64": "npm run pre-build-exe && nexe -i runtime/index.js -t mac-x64-12.16.2 -o github-security-report-mac-x64", 10 | "build-exe-windows-x64": "npm run pre-build-exe && nexe -i runtime/index.js -t windows-x64-12.16.2 -o github-security-report", 11 | "package": "npm run build && ncc build --source-map", 12 | "postpack": "tarball=$(npm list - depth 0 | sed 's/@/-/g; s/ .*/.tgz/g; 1q;'); tar -tf $tarball | sed 's/^package\\///' | zip -@r github-security-report-bundle", 13 | "test": "mocha --recursive -r ts-node/register \"src/**/*.test.ts\"" 14 | }, 15 | "keywords": [], 16 | "author": "Peter Murray", 17 | "license": "MIT", 18 | "dependencies": { 19 | "@actions/core": "^1.2.6", 20 | "@actions/io": "^1.0.2", 21 | "@octokit/rest": "^18.0.15", 22 | "commander": "^7.0.0", 23 | "nunjucks": "^3.2.2", 24 | "puppeteer-core": "^5.5.0" 25 | }, 26 | "devDependencies": { 27 | "@octokit/types": "^5.5.0", 28 | "@types/chai": "^4.2.14", 29 | "@types/mocha": "^8.2.0", 30 | "@types/node": "^12.19.15", 31 | "@types/puppeteer-core": "^2.1.0", 32 | "@vercel/ncc": "^0.27.0", 33 | "chai": "^4.2.0", 34 | "mocha": "^8.2.1", 35 | "nexe": "^4.0.0-beta.17", 36 | "ts-node": "^9.1.1", 37 | "typescript": "^4.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/__old_to_remove/java_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", 3 | "version" : "2.1.0", 4 | "runs" : [ { 5 | "tool" : { 6 | "driver" : { 7 | "name" : "CodeQL", 8 | "organization" : "GitHub", 9 | "semanticVersion" : "2.2.5", 10 | "rules" : [ 11 | { 12 | "id" : "java/unsafe-deserialization", 13 | "name" : "java/unsafe-deserialization", 14 | "shortDescription" : { 15 | "text" : "Deserialization of user-controlled data" 16 | }, 17 | "fullDescription" : { 18 | "text" : "Deserializing user-controlled data may allow attackers to execute arbitrary code." 19 | }, 20 | "defaultConfiguration" : { 21 | "level" : "error" 22 | }, 23 | "properties" : { 24 | "tags" : [ "security", "external/cwe/cwe-502" ], 25 | "kind" : "path-problem", 26 | "precision" : "high", 27 | "name" : "Deserialization of user-controlled data", 28 | "description" : "Deserializing user-controlled data may allow attackers to\n execute arbitrary code.", 29 | "id" : "java/unsafe-deserialization", 30 | "problem.severity" : "error" 31 | } 32 | }, { 33 | "id" : "java/ldap-injection", 34 | "name" : "java/ldap-injection", 35 | "shortDescription" : { 36 | "text" : "LDAP query built from user-controlled sources" 37 | }, 38 | "fullDescription" : { 39 | "text" : "Building an LDAP query from user-controlled sources is vulnerable to insertion of malicious LDAP code by the user." 40 | }, 41 | "defaultConfiguration" : { 42 | "level" : "error" 43 | }, 44 | "properties" : { 45 | "tags" : [ "security", "external/cwe/cwe-090" ], 46 | "kind" : "path-problem", 47 | "precision" : "high", 48 | "name" : "LDAP query built from user-controlled sources", 49 | "description" : "Building an LDAP query from user-controlled sources is vulnerable to insertion of\n malicious LDAP code by the user.", 50 | "id" : "java/ldap-injection", 51 | "problem.severity" : "error" 52 | } 53 | }, { 54 | "id" : "java/xss", 55 | "name" : "java/xss", 56 | "shortDescription" : { 57 | "text" : "Cross-site scripting" 58 | }, 59 | "fullDescription" : { 60 | "text" : "Writing user input directly to a web page allows for a cross-site scripting vulnerability." 61 | }, 62 | "defaultConfiguration" : { 63 | "level" : "error" 64 | }, 65 | "properties" : { 66 | "tags" : [ "security", "external/cwe/cwe-079" ], 67 | "kind" : "path-problem", 68 | "precision" : "high", 69 | "name" : "Cross-site scripting", 70 | "description" : "Writing user input directly to a web page\n allows for a cross-site scripting vulnerability.", 71 | "id" : "java/xss", 72 | "problem.severity" : "error" 73 | } 74 | }, { 75 | "id" : "java/insecure-cookie", 76 | "name" : "java/insecure-cookie", 77 | "shortDescription" : { 78 | "text" : "Failure to use secure cookies" 79 | }, 80 | "fullDescription" : { 81 | "text" : "Insecure cookies may be sent in cleartext, which makes them vulnerable to interception." 82 | }, 83 | "defaultConfiguration" : { 84 | "level" : "error" 85 | }, 86 | "properties" : { 87 | "tags" : [ "security", "external/cwe/cwe-614" ], 88 | "kind" : "problem", 89 | "precision" : "high", 90 | "name" : "Failure to use secure cookies", 91 | "description" : "Insecure cookies may be sent in cleartext, which makes them vulnerable to\n interception.", 92 | "id" : "java/insecure-cookie", 93 | "problem.severity" : "error" 94 | } 95 | }, { 96 | "id" : "java/maven/non-https-url", 97 | "name" : "java/maven/non-https-url", 98 | "shortDescription" : { 99 | "text" : "Failure to use HTTPS or SFTP URL in Maven artifact upload/download" 100 | }, 101 | "fullDescription" : { 102 | "text" : "Non-HTTPS connections can be intercepted by third parties." 103 | }, 104 | "defaultConfiguration" : { 105 | "level" : "error" 106 | }, 107 | "properties" : { 108 | "tags" : [ "security", "external/cwe/cwe-300", "external/cwe/cwe-319", "external/cwe/cwe-494", "external/cwe/cwe-829" ], 109 | "kind" : "problem", 110 | "precision" : "very-high", 111 | "name" : "Failure to use HTTPS or SFTP URL in Maven artifact upload/download", 112 | "description" : "Non-HTTPS connections can be intercepted by third parties.", 113 | "id" : "java/maven/non-https-url", 114 | "problem.severity" : "error" 115 | } 116 | }, { 117 | "id" : "java/tainted-format-string", 118 | "name" : "java/tainted-format-string", 119 | "shortDescription" : { 120 | "text" : "Use of externally-controlled format string" 121 | }, 122 | "fullDescription" : { 123 | "text" : "Using external input in format strings can lead to exceptions or information leaks." 124 | }, 125 | "defaultConfiguration" : { 126 | "level" : "error" 127 | }, 128 | "properties" : { 129 | "tags" : [ "security", "external/cwe/cwe-134" ], 130 | "kind" : "path-problem", 131 | "precision" : "high", 132 | "name" : "Use of externally-controlled format string", 133 | "description" : "Using external input in format strings can lead to exceptions or information leaks.", 134 | "id" : "java/tainted-format-string", 135 | "problem.severity" : "error" 136 | } 137 | }, { 138 | "id" : "java/sql-injection", 139 | "name" : "java/sql-injection", 140 | "shortDescription" : { 141 | "text" : "Query built from user-controlled sources" 142 | }, 143 | "fullDescription" : { 144 | "text" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of malicious code by the user." 145 | }, 146 | "defaultConfiguration" : { 147 | "level" : "error" 148 | }, 149 | "properties" : { 150 | "tags" : [ "security", "external/cwe/cwe-089" ], 151 | "kind" : "path-problem", 152 | "precision" : "high", 153 | "name" : "Query built from user-controlled sources", 154 | "description" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of\n malicious code by the user.", 155 | "id" : "java/sql-injection", 156 | "problem.severity" : "error" 157 | } 158 | }, { 159 | "id" : "java/concatenated-sql-query", 160 | "name" : "java/concatenated-sql-query", 161 | "shortDescription" : { 162 | "text" : "Query built without neutralizing special characters" 163 | }, 164 | "fullDescription" : { 165 | "text" : "Building a SQL or Java Persistence query without escaping or otherwise neutralizing any special characters is vulnerable to insertion of malicious code." 166 | }, 167 | "defaultConfiguration" : { 168 | "level" : "error" 169 | }, 170 | "properties" : { 171 | "tags" : [ "security", "external/cwe/cwe-089" ], 172 | "kind" : "problem", 173 | "precision" : "high", 174 | "name" : "Query built without neutralizing special characters", 175 | "description" : "Building a SQL or Java Persistence query without escaping or otherwise neutralizing any special\n characters is vulnerable to insertion of malicious code.", 176 | "id" : "java/concatenated-sql-query", 177 | "problem.severity" : "error" 178 | } 179 | }, { 180 | "id" : "java/tainted-numeric-cast", 181 | "name" : "java/tainted-numeric-cast", 182 | "shortDescription" : { 183 | "text" : "User-controlled data in numeric cast" 184 | }, 185 | "fullDescription" : { 186 | "text" : "Casting user-controlled numeric data to a narrower type without validation can cause unexpected truncation." 187 | }, 188 | "defaultConfiguration" : { 189 | "level" : "error" 190 | }, 191 | "properties" : { 192 | "tags" : [ "security", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 193 | "kind" : "path-problem", 194 | "precision" : "high", 195 | "name" : "User-controlled data in numeric cast", 196 | "description" : "Casting user-controlled numeric data to a narrower type without validation\n can cause unexpected truncation.", 197 | "id" : "java/tainted-numeric-cast", 198 | "problem.severity" : "error" 199 | } 200 | }, { 201 | "id" : "java/tainted-permissions-check", 202 | "name" : "java/tainted-permissions-check", 203 | "shortDescription" : { 204 | "text" : "User-controlled data used in permissions check" 205 | }, 206 | "fullDescription" : { 207 | "text" : "Using user-controlled data in a permissions check may result in inappropriate permissions being granted." 208 | }, 209 | "defaultConfiguration" : { 210 | "level" : "error" 211 | }, 212 | "properties" : { 213 | "tags" : [ "security", "external/cwe/cwe-807", "external/cwe/cwe-290" ], 214 | "kind" : "path-problem", 215 | "precision" : "high", 216 | "name" : "User-controlled data used in permissions check", 217 | "description" : "Using user-controlled data in a permissions check may result in inappropriate\n permissions being granted.", 218 | "id" : "java/tainted-permissions-check", 219 | "problem.severity" : "error" 220 | } 221 | }, { 222 | "id" : "java/spring-disabled-csrf-protection", 223 | "name" : "java/spring-disabled-csrf-protection", 224 | "shortDescription" : { 225 | "text" : "Disabled Spring CSRF protection" 226 | }, 227 | "fullDescription" : { 228 | "text" : "Disabling CSRF protection makes the application vulnerable to a Cross-Site Request Forgery (CSRF) attack." 229 | }, 230 | "defaultConfiguration" : { 231 | "level" : "error" 232 | }, 233 | "properties" : { 234 | "tags" : [ "security", "external/cwe/cwe-352" ], 235 | "kind" : "problem", 236 | "precision" : "high", 237 | "name" : "Disabled Spring CSRF protection", 238 | "description" : "Disabling CSRF protection makes the application vulnerable to\n a Cross-Site Request Forgery (CSRF) attack.", 239 | "id" : "java/spring-disabled-csrf-protection", 240 | "problem.severity" : "error" 241 | } 242 | }, { 243 | "id" : "java/cleartext-storage-in-cookie", 244 | "name" : "java/cleartext-storage-in-cookie", 245 | "shortDescription" : { 246 | "text" : "Cleartext storage of sensitive information in cookie" 247 | }, 248 | "fullDescription" : { 249 | "text" : "Storing sensitive information in cleartext can expose it to an attacker." 250 | }, 251 | "defaultConfiguration" : { 252 | "level" : "error" 253 | }, 254 | "properties" : { 255 | "tags" : [ "security", "external/cwe/cwe-315" ], 256 | "kind" : "problem", 257 | "precision" : "high", 258 | "name" : "Cleartext storage of sensitive information in cookie", 259 | "description" : "Storing sensitive information in cleartext can expose it to an attacker.", 260 | "id" : "java/cleartext-storage-in-cookie", 261 | "problem.severity" : "error" 262 | } 263 | }, { 264 | "id" : "java/unvalidated-url-redirection", 265 | "name" : "java/unvalidated-url-redirection", 266 | "shortDescription" : { 267 | "text" : "URL redirection from remote source" 268 | }, 269 | "fullDescription" : { 270 | "text" : "URL redirection based on unvalidated user-input may cause redirection to malicious web sites." 271 | }, 272 | "defaultConfiguration" : { 273 | "level" : "error" 274 | }, 275 | "properties" : { 276 | "tags" : [ "security", "external/cwe/cwe-601" ], 277 | "kind" : "path-problem", 278 | "precision" : "high", 279 | "name" : "URL redirection from remote source", 280 | "description" : "URL redirection based on unvalidated user-input\n may cause redirection to malicious web sites.", 281 | "id" : "java/unvalidated-url-redirection", 282 | "problem.severity" : "error" 283 | } 284 | }, { 285 | "id" : "java/stack-trace-exposure", 286 | "name" : "java/stack-trace-exposure", 287 | "shortDescription" : { 288 | "text" : "Information exposure through a stack trace" 289 | }, 290 | "fullDescription" : { 291 | "text" : "Information from a stack trace propagates to an external user. Stack traces can unintentionally reveal implementation details that are useful to an attacker for developing a subsequent exploit." 292 | }, 293 | "defaultConfiguration" : { 294 | "level" : "error" 295 | }, 296 | "properties" : { 297 | "tags" : [ "security", "external/cwe/cwe-209", "external/cwe/cwe-497" ], 298 | "kind" : "problem", 299 | "precision" : "high", 300 | "name" : "Information exposure through a stack trace", 301 | "description" : "Information from a stack trace propagates to an external user.\n Stack traces can unintentionally reveal implementation details\n that are useful to an attacker for developing a subsequent exploit.", 302 | "id" : "java/stack-trace-exposure", 303 | "problem.severity" : "error" 304 | } 305 | }, { 306 | "id" : "java/netty-http-response-splitting", 307 | "name" : "java/netty-http-response-splitting", 308 | "shortDescription" : { 309 | "text" : "Disabled Netty HTTP header validation" 310 | }, 311 | "fullDescription" : { 312 | "text" : "Disabling HTTP header validation makes code vulnerable to attack by header splitting if user input is written directly to an HTTP header." 313 | }, 314 | "defaultConfiguration" : { 315 | "level" : "error" 316 | }, 317 | "properties" : { 318 | "tags" : [ "security", "external/cwe/cwe-113" ], 319 | "kind" : "problem", 320 | "precision" : "high", 321 | "name" : "Disabled Netty HTTP header validation", 322 | "description" : "Disabling HTTP header validation makes code vulnerable to\n attack by header splitting if user input is written directly to\n an HTTP header.", 323 | "id" : "java/netty-http-response-splitting", 324 | "problem.severity" : "error" 325 | } 326 | }, { 327 | "id" : "java/http-response-splitting", 328 | "name" : "java/http-response-splitting", 329 | "shortDescription" : { 330 | "text" : "HTTP response splitting" 331 | }, 332 | "fullDescription" : { 333 | "text" : "Writing user input directly to an HTTP header makes code vulnerable to attack by header splitting." 334 | }, 335 | "defaultConfiguration" : { 336 | "level" : "error" 337 | }, 338 | "properties" : { 339 | "tags" : [ "security", "external/cwe/cwe-113" ], 340 | "kind" : "path-problem", 341 | "precision" : "high", 342 | "name" : "HTTP response splitting", 343 | "description" : "Writing user input directly to an HTTP header\n makes code vulnerable to attack by header splitting.", 344 | "id" : "java/http-response-splitting", 345 | "problem.severity" : "error" 346 | } 347 | }, { 348 | "id" : "java/zipslip", 349 | "name" : "java/zipslip", 350 | "shortDescription" : { 351 | "text" : "Arbitrary file write during archive extraction (\"Zip Slip\")" 352 | }, 353 | "fullDescription" : { 354 | "text" : "Extracting files from a malicious archive without validating that the destination file path is within the destination directory can cause files outside the destination directory to be overwritten." 355 | }, 356 | "defaultConfiguration" : { 357 | "level" : "error" 358 | }, 359 | "properties" : { 360 | "tags" : [ "security", "external/cwe/cwe-022" ], 361 | "kind" : "path-problem", 362 | "precision" : "high", 363 | "name" : "Arbitrary file write during archive extraction (\"Zip Slip\")", 364 | "description" : "Extracting files from a malicious archive without validating that the\n destination file path is within the destination directory can cause files outside\n the destination directory to be overwritten.", 365 | "id" : "java/zipslip", 366 | "problem.severity" : "error" 367 | } 368 | }, { 369 | "id" : "java/path-injection", 370 | "name" : "java/path-injection", 371 | "shortDescription" : { 372 | "text" : "Uncontrolled data used in path expression" 373 | }, 374 | "fullDescription" : { 375 | "text" : "Accessing paths influenced by users can allow an attacker to access unexpected resources." 376 | }, 377 | "defaultConfiguration" : { 378 | "level" : "error" 379 | }, 380 | "properties" : { 381 | "tags" : [ "security", "external/cwe/cwe-022", "external/cwe/cwe-023", "external/cwe/cwe-036", "external/cwe/cwe-073" ], 382 | "kind" : "path-problem", 383 | "precision" : "high", 384 | "name" : "Uncontrolled data used in path expression", 385 | "description" : "Accessing paths influenced by users can allow an attacker to access unexpected resources.", 386 | "id" : "java/path-injection", 387 | "problem.severity" : "error" 388 | } 389 | }, { 390 | "id" : "java/predictable-seed", 391 | "name" : "java/predictable-seed", 392 | "shortDescription" : { 393 | "text" : "Use of a predictable seed in a secure random number generator" 394 | }, 395 | "fullDescription" : { 396 | "text" : "Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it." 397 | }, 398 | "defaultConfiguration" : { 399 | "level" : "error" 400 | }, 401 | "properties" : { 402 | "tags" : [ "security" ], 403 | "kind" : "problem", 404 | "precision" : "high", 405 | "name" : "Use of a predictable seed in a secure random number generator", 406 | "description" : "Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it.", 407 | "id" : "java/predictable-seed", 408 | "problem.severity" : "error" 409 | } 410 | }, { 411 | "id" : "java/command-line-injection", 412 | "name" : "java/command-line-injection", 413 | "shortDescription" : { 414 | "text" : "Uncontrolled command line" 415 | }, 416 | "fullDescription" : { 417 | "text" : "Using externally controlled strings in a command line is vulnerable to malicious changes in the strings." 418 | }, 419 | "defaultConfiguration" : { 420 | "level" : "error" 421 | }, 422 | "properties" : { 423 | "tags" : [ "security", "external/cwe/cwe-078", "external/cwe/cwe-088" ], 424 | "kind" : "path-problem", 425 | "precision" : "high", 426 | "name" : "Uncontrolled command line", 427 | "description" : "Using externally controlled strings in a command line is vulnerable to malicious\n changes in the strings.", 428 | "id" : "java/command-line-injection", 429 | "problem.severity" : "error" 430 | } 431 | }, { 432 | "id" : "java/concatenated-command-line", 433 | "name" : "java/concatenated-command-line", 434 | "shortDescription" : { 435 | "text" : "Building a command line with string concatenation" 436 | }, 437 | "fullDescription" : { 438 | "text" : "Using concatenated strings in a command line is vulnerable to malicious insertion of special characters in the strings." 439 | }, 440 | "defaultConfiguration" : { 441 | "level" : "error" 442 | }, 443 | "properties" : { 444 | "tags" : [ "security", "external/cwe/cwe-078", "external/cwe/cwe-088" ], 445 | "kind" : "problem", 446 | "precision" : "high", 447 | "name" : "Building a command line with string concatenation", 448 | "description" : "Using concatenated strings in a command line is vulnerable to malicious\n insertion of special characters in the strings.", 449 | "id" : "java/concatenated-command-line", 450 | "problem.severity" : "error" 451 | } 452 | }, { 453 | "id" : "java/world-writable-file-read", 454 | "name" : "java/world-writable-file-read", 455 | "shortDescription" : { 456 | "text" : "Reading from a world writable file" 457 | }, 458 | "fullDescription" : { 459 | "text" : "Reading from a file which is set as world writable is dangerous because the file may be modified or removed by external actors." 460 | }, 461 | "defaultConfiguration" : { 462 | "level" : "error" 463 | }, 464 | "properties" : { 465 | "tags" : [ "security", "external/cwe/cwe-732" ], 466 | "kind" : "problem", 467 | "precision" : "high", 468 | "name" : "Reading from a world writable file", 469 | "description" : "Reading from a file which is set as world writable is dangerous because\n the file may be modified or removed by external actors.", 470 | "id" : "java/world-writable-file-read", 471 | "problem.severity" : "error" 472 | } 473 | }, { 474 | "id" : "java/xxe", 475 | "name" : "java/xxe", 476 | "shortDescription" : { 477 | "text" : "Resolving XML external entity in user-controlled data" 478 | }, 479 | "fullDescription" : { 480 | "text" : "Parsing user-controlled XML documents and allowing expansion of external entity references may lead to disclosure of confidential data or denial of service." 481 | }, 482 | "defaultConfiguration" : { 483 | "level" : "error" 484 | }, 485 | "properties" : { 486 | "tags" : [ "security", "external/cwe/cwe-611" ], 487 | "kind" : "path-problem", 488 | "precision" : "high", 489 | "name" : "Resolving XML external entity in user-controlled data", 490 | "description" : "Parsing user-controlled XML documents and allowing expansion of external entity\n references may lead to disclosure of confidential data or denial of service.", 491 | "id" : "java/xxe", 492 | "problem.severity" : "error" 493 | } 494 | }, { 495 | "id" : "java/implicit-cast-in-compound-assignment", 496 | "name" : "java/implicit-cast-in-compound-assignment", 497 | "shortDescription" : { 498 | "text" : "Implicit narrowing conversion in compound assignment" 499 | }, 500 | "fullDescription" : { 501 | "text" : "Compound assignment statements (for example 'intvar += longvar') that implicitly cast a value of a wider type to a narrower type may result in information loss and numeric errors such as overflows." 502 | }, 503 | "defaultConfiguration" : { }, 504 | "properties" : { 505 | "tags" : [ "reliability", "security", "external/cwe/cwe-190", "external/cwe/cwe-192", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 506 | "kind" : "problem", 507 | "precision" : "very-high", 508 | "name" : "Implicit narrowing conversion in compound assignment", 509 | "description" : "Compound assignment statements (for example 'intvar += longvar') that implicitly\n cast a value of a wider type to a narrower type may result in information loss and\n numeric errors such as overflows.", 510 | "id" : "java/implicit-cast-in-compound-assignment", 511 | "problem.severity" : "warning" 512 | } 513 | }, { 514 | "id" : "java/integer-multiplication-cast-to-long", 515 | "name" : "java/integer-multiplication-cast-to-long", 516 | "shortDescription" : { 517 | "text" : "Result of multiplication cast to wider type" 518 | }, 519 | "fullDescription" : { 520 | "text" : "Casting the result of a multiplication to a wider type instead of casting before the multiplication may cause overflow." 521 | }, 522 | "defaultConfiguration" : { }, 523 | "properties" : { 524 | "tags" : [ "reliability", "security", "correctness", "types", "external/cwe/cwe-190", "external/cwe/cwe-192", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 525 | "kind" : "problem", 526 | "precision" : "very-high", 527 | "name" : "Result of multiplication cast to wider type", 528 | "description" : "Casting the result of a multiplication to a wider type instead of casting\n before the multiplication may cause overflow.", 529 | "id" : "java/integer-multiplication-cast-to-long", 530 | "problem.severity" : "warning" 531 | } 532 | } ] 533 | } 534 | }, 535 | "results" : [ ], 536 | "columnKind" : "utf16CodeUnits", 537 | "properties" : { 538 | "semmle.formatSpecifier" : "sarif-latest" 539 | } 540 | } ] 541 | } -------------------------------------------------------------------------------- /samples/__old_to_remove/sample_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": { 3 | "owner": "", 4 | "name": "" 5 | }, 6 | "vulnerabilities": [ 7 | 8 | ], 9 | "codeScanning": { 10 | "open": [], 11 | "closed": [], 12 | "rules": [] 13 | } 14 | } -------------------------------------------------------------------------------- /samples/__old_to_remove/summary/small.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "deps": { 4 | "manifests": { 5 | "processed": [], 6 | "unprocessed": [] 7 | }, 8 | "totalDependencies": 0, 9 | "dependencies": {} 10 | }, 11 | "vulnerabilities": { 12 | "CRITICAL": [], 13 | "HIGH": [], 14 | "LOW": [], 15 | "MODERATE": [] 16 | } 17 | }, 18 | "codeScanning": { 19 | "cwes": [ 20 | "cwe-022", 21 | "cwe-023", 22 | "cwe-036", 23 | "cwe-073", 24 | "cwe-078", 25 | "cwe-079", 26 | "cwe-088", 27 | "cwe-089", 28 | "cwe-090", 29 | "cwe-113", 30 | "cwe-129", 31 | "cwe-134", 32 | "cwe-190" 33 | ], 34 | "rules": [ 35 | { 36 | "name": "java/unsafe-deserialization", 37 | "severity": "error", 38 | "precision": "high", 39 | "kind": "path-problem", 40 | "shortDescription": "Deserialization of user-controlled data", 41 | "description": "Deserializing user-controlled data may allow attackers to execute arbitrary code.", 42 | "tags": [ 43 | "security", 44 | "external/cwe/cwe-502" 45 | ], 46 | "cwe": [ 47 | "cwe-502" 48 | ] 49 | }, 50 | { 51 | "name": "java/call-to-object-tostring", 52 | "severity": "recommendation", 53 | "precision": "high", 54 | "kind": "problem", 55 | "shortDescription": "Use of default toString()", 56 | "description": "Calling the default implementation of 'toString' returns a value that is unlikely to be what you expect.", 57 | "tags": [ 58 | "reliability", 59 | "maintainability" 60 | ], 61 | "cwe": [] 62 | }, 63 | { 64 | "name": "java/constant-comparison", 65 | "severity": "warning", 66 | "precision": "very-high", 67 | "kind": "problem", 68 | "shortDescription": "Useless comparison test", 69 | "description": "A comparison operation that always evaluates to true or always evaluates to false may indicate faulty logic and may result in dead code.", 70 | "tags": [ 71 | "correctness", 72 | "logic", 73 | "external/cwe/cwe-570", 74 | "external/cwe/cwe-571" 75 | ], 76 | "cwe": [ 77 | "cwe-570", 78 | "cwe-571" 79 | ] 80 | } 81 | ], 82 | "open": { 83 | "warning": [ 84 | { 85 | "tool": "CodeQL", 86 | "name": "Unsafe use of getResource", 87 | "open": true, 88 | "created": "2020-09-04T13:39:16Z", 89 | "url": "https://api.github.com/repos/octodemo/demo-octopus/code-scanning/alerts/55", 90 | "rule": { 91 | "id": "java/unsafe-get-resource", 92 | "details": { 93 | "name": "java/unsafe-get-resource", 94 | "shortDescription": "Unsafe use of getResource", 95 | "description": "Calling 'this.getClass().getResource()' may yield unexpected results if called from a subclass in another package.", 96 | "tags": [ 97 | "reliability", 98 | "maintainability" 99 | ], 100 | "cwes": [] 101 | } 102 | } 103 | } 104 | ] 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /samples/__old_to_remove/vulnerabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "repository": { 4 | "vulnerabilityAlerts": { 5 | "totalCount": 16, 6 | "pageInfo": { 7 | "hasNextPage": false, 8 | "endCursor": "Y3Vyc29yOnYyOpHOFkH69w==" 9 | }, 10 | "nodes": [ 11 | { 12 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3NDY=", 13 | "createdAt": "2020-09-05T14:05:42Z", 14 | "dismisser": null, 15 | "dismissedAt": null, 16 | "dismissReason": null, 17 | "vulnerableManifestFilename": "package-lock.json", 18 | "vulnerableRequirements": "= 4.2.1", 19 | "vulnerableManifestPath": "docs/package-lock.json", 20 | "securityVulnerability": { 21 | "package": { 22 | "ecosystem": "NPM", 23 | "name": "bootstrap" 24 | }, 25 | "severity": "MODERATE", 26 | "vulnerableVersionRange": ">= 4.0.0, < 4.3.1" 27 | }, 28 | "securityAdvisory": { 29 | "databaseId": 1276, 30 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXdoNzctM3g0bS00cTln", 31 | "summary": "Moderate severity vulnerability that affects bootstrap and bootstrap-sass", 32 | "severity": "MODERATE", 33 | "description": "In Bootstrap 4 before 4.3.1 and Bootstrap 3 before 3.4.1, XSS is possible in the tooltip or popover data-template attribute. For more information, see: https://blog.getbootstrap.com/2019/02/13/bootstrap-4-3-1-and-3-4-1/", 34 | "ghsaId": "GHSA-wh77-3x4m-4q9g", 35 | "identifiers": [ 36 | { 37 | "type": "GHSA", 38 | "value": "GHSA-wh77-3x4m-4q9g" 39 | }, 40 | { 41 | "type": "CVE", 42 | "value": "CVE-2019-8331" 43 | } 44 | ], 45 | "permalink": "https://github.com/advisories/GHSA-wh77-3x4m-4q9g", 46 | "publishedAt": "2019-02-22T20:54:27Z" 47 | } 48 | }, 49 | { 50 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3Njc=", 51 | "createdAt": "2020-09-05T14:05:42Z", 52 | "dismisser": null, 53 | "dismissedAt": null, 54 | "dismissReason": null, 55 | "vulnerableManifestFilename": "package-lock.json", 56 | "vulnerableRequirements": "= 0.17.1", 57 | "vulnerableManifestPath": "docs/package-lock.json", 58 | "securityVulnerability": { 59 | "package": { 60 | "ecosystem": "NPM", 61 | "name": "axios" 62 | }, 63 | "severity": "MODERATE", 64 | "vulnerableVersionRange": "<= 0.18.0" 65 | }, 66 | "securityAdvisory": { 67 | "databaseId": 1398, 68 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTQyeHctMnh2Yy1xeDht", 69 | "summary": "Denial of Service in axios", 70 | "severity": "MODERATE", 71 | "description": "Versions of `axios` prior to 0.18.1 are vulnerable to Denial of Service. If a request exceeds the `maxContentLength` property, the package prints an error but does not stop the request. This may cause high CPU usage and lead to Denial of Service.\n\n\n## Recommendation\n\nUpgrade to 0.18.1 or later.", 72 | "ghsaId": "GHSA-42xw-2xvc-qx8m", 73 | "identifiers": [ 74 | { 75 | "type": "GHSA", 76 | "value": "GHSA-42xw-2xvc-qx8m" 77 | }, 78 | { 79 | "type": "CVE", 80 | "value": "CVE-2019-10742" 81 | } 82 | ], 83 | "permalink": "https://github.com/advisories/GHSA-42xw-2xvc-qx8m", 84 | "publishedAt": "2019-05-29T18:04:45Z" 85 | } 86 | }, 87 | { 88 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3NzU=", 89 | "createdAt": "2020-09-05T14:05:43Z", 90 | "dismisser": null, 91 | "dismissedAt": null, 92 | "dismissReason": null, 93 | "vulnerableManifestFilename": "package-lock.json", 94 | "vulnerableRequirements": "= 2.2.1", 95 | "vulnerableManifestPath": "docs/package-lock.json", 96 | "securityVulnerability": { 97 | "package": { 98 | "ecosystem": "NPM", 99 | "name": "tar" 100 | }, 101 | "severity": "HIGH", 102 | "vulnerableVersionRange": "< 2.2.2" 103 | }, 104 | "securityAdvisory": { 105 | "databaseId": 1383, 106 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWo0NG0tcW02cC1ocDdt", 107 | "summary": "Arbitrary File Overwrite in tar", 108 | "severity": "HIGH", 109 | "description": "Versions of `tar` prior to 4.4.2 for 4.x and 2.2.2 for 2.x are vulnerable to Arbitrary File Overwrite. Extracting tarballs containing a hardlink to a file that already exists in the system, and a file that matches the hardlink will overwrite the system's file with the contents of the extracted file.\n\n\n## Recommendation\n\nFor tar 4.x, upgrade to version 4.4.2 or later.\nFor tar 2.x, upgrade to version 2.2.2 or later.", 110 | "ghsaId": "GHSA-j44m-qm6p-hp7m", 111 | "identifiers": [ 112 | { 113 | "type": "GHSA", 114 | "value": "GHSA-j44m-qm6p-hp7m" 115 | }, 116 | { 117 | "type": "CVE", 118 | "value": "CVE-2018-20834" 119 | } 120 | ], 121 | "permalink": "https://github.com/advisories/GHSA-j44m-qm6p-hp7m", 122 | "publishedAt": "2019-05-01T18:37:31Z" 123 | } 124 | }, 125 | { 126 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3ODI=", 127 | "createdAt": "2020-09-05T14:05:43Z", 128 | "dismisser": null, 129 | "dismissedAt": null, 130 | "dismissReason": null, 131 | "vulnerableManifestFilename": "package-lock.json", 132 | "vulnerableRequirements": "= 1.0.11", 133 | "vulnerableManifestPath": "docs/package-lock.json", 134 | "securityVulnerability": { 135 | "package": { 136 | "ecosystem": "NPM", 137 | "name": "fstream" 138 | }, 139 | "severity": "HIGH", 140 | "vulnerableVersionRange": "< 1.0.12" 141 | }, 142 | "securityAdvisory": { 143 | "databaseId": 1431, 144 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXhmN3ctcjQ1My1tNTZj", 145 | "summary": "Arbitrary File Overwrite in fstream", 146 | "severity": "HIGH", 147 | "description": "Versions of `fstream` prior to 1.0.12 are vulnerable to Arbitrary File Overwrite. Extracting tarballs containing a hardlink to a file that already exists in the system and a file that matches the hardlink will overwrite the system's file with the contents of the extracted file. The `fstream.DirWriter()` function is vulnerable.\n\n\n## Recommendation\n\nUpgrade to version 1.0.12 or later.", 148 | "ghsaId": "GHSA-xf7w-r453-m56c", 149 | "identifiers": [ 150 | { 151 | "type": "GHSA", 152 | "value": "GHSA-xf7w-r453-m56c" 153 | }, 154 | { 155 | "type": "CVE", 156 | "value": "CVE-2019-13173" 157 | } 158 | ], 159 | "permalink": "https://github.com/advisories/GHSA-xf7w-r453-m56c", 160 | "publishedAt": "2019-05-30T17:19:34Z" 161 | } 162 | }, 163 | { 164 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3ODg=", 165 | "createdAt": "2020-09-05T14:05:43Z", 166 | "dismisser": null, 167 | "dismissedAt": null, 168 | "dismissReason": null, 169 | "vulnerableManifestFilename": "package-lock.json", 170 | "vulnerableRequirements": "= 4.17.11", 171 | "vulnerableManifestPath": "docs/package-lock.json", 172 | "securityVulnerability": { 173 | "package": { 174 | "ecosystem": "NPM", 175 | "name": "lodash" 176 | }, 177 | "severity": "HIGH", 178 | "vulnerableVersionRange": "< 4.17.12" 179 | }, 180 | "securityAdvisory": { 181 | "databaseId": 1579, 182 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWpmODUtY3BjcC1qNjk1", 183 | "summary": "Prototype Pollution in lodash", 184 | "severity": "HIGH", 185 | "description": "Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution. The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.\n\n\n\n\n## Recommendation\n\nUpdate to version 4.17.12 or later.", 186 | "ghsaId": "GHSA-jf85-cpcp-j695", 187 | "identifiers": [ 188 | { 189 | "type": "GHSA", 190 | "value": "GHSA-jf85-cpcp-j695" 191 | }, 192 | { 193 | "type": "CVE", 194 | "value": "CVE-2019-10744" 195 | } 196 | ], 197 | "permalink": "https://github.com/advisories/GHSA-jf85-cpcp-j695", 198 | "publishedAt": "2019-07-10T19:45:23Z" 199 | } 200 | }, 201 | { 202 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3OTM=", 203 | "createdAt": "2020-09-05T14:05:43Z", 204 | "dismisser": null, 205 | "dismissedAt": null, 206 | "dismissReason": null, 207 | "vulnerableManifestFilename": "package-lock.json", 208 | "vulnerableRequirements": "= 4.4.0", 209 | "vulnerableManifestPath": "docs/package-lock.json", 210 | "securityVulnerability": { 211 | "package": { 212 | "ecosystem": "NPM", 213 | "name": "lodash.template" 214 | }, 215 | "severity": "HIGH", 216 | "vulnerableVersionRange": "< 4.5.0" 217 | }, 218 | "securityAdvisory": { 219 | "databaseId": 1579, 220 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWpmODUtY3BjcC1qNjk1", 221 | "summary": "Prototype Pollution in lodash", 222 | "severity": "HIGH", 223 | "description": "Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution. The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.\n\n\n\n\n## Recommendation\n\nUpdate to version 4.17.12 or later.", 224 | "ghsaId": "GHSA-jf85-cpcp-j695", 225 | "identifiers": [ 226 | { 227 | "type": "GHSA", 228 | "value": "GHSA-jf85-cpcp-j695" 229 | }, 230 | { 231 | "type": "CVE", 232 | "value": "CVE-2019-10744" 233 | } 234 | ], 235 | "permalink": "https://github.com/advisories/GHSA-jf85-cpcp-j695", 236 | "publishedAt": "2019-07-10T19:45:23Z" 237 | } 238 | }, 239 | { 240 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI3OTc=", 241 | "createdAt": "2020-09-05T14:05:43Z", 242 | "dismisser": null, 243 | "dismissedAt": null, 244 | "dismissReason": null, 245 | "vulnerableManifestFilename": "package-lock.json", 246 | "vulnerableRequirements": "= 4.6.1", 247 | "vulnerableManifestPath": "docs/package-lock.json", 248 | "securityVulnerability": { 249 | "package": { 250 | "ecosystem": "NPM", 251 | "name": "lodash.mergewith" 252 | }, 253 | "severity": "HIGH", 254 | "vulnerableVersionRange": "< 4.6.2" 255 | }, 256 | "securityAdvisory": { 257 | "databaseId": 1579, 258 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWpmODUtY3BjcC1qNjk1", 259 | "summary": "Prototype Pollution in lodash", 260 | "severity": "HIGH", 261 | "description": "Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution. The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.\n\n\n\n\n## Recommendation\n\nUpdate to version 4.17.12 or later.", 262 | "ghsaId": "GHSA-jf85-cpcp-j695", 263 | "identifiers": [ 264 | { 265 | "type": "GHSA", 266 | "value": "GHSA-jf85-cpcp-j695" 267 | }, 268 | { 269 | "type": "CVE", 270 | "value": "CVE-2019-10744" 271 | } 272 | ], 273 | "permalink": "https://github.com/advisories/GHSA-jf85-cpcp-j695", 274 | "publishedAt": "2019-07-10T19:45:23Z" 275 | } 276 | }, 277 | { 278 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MDg=", 279 | "createdAt": "2020-09-05T14:05:43Z", 280 | "dismisser": null, 281 | "dismissedAt": null, 282 | "dismissReason": null, 283 | "vulnerableManifestFilename": "package-lock.json", 284 | "vulnerableRequirements": "= 1.3.1", 285 | "vulnerableManifestPath": "docs/package-lock.json", 286 | "securityVulnerability": { 287 | "package": { 288 | "ecosystem": "NPM", 289 | "name": "mixin-deep" 290 | }, 291 | "severity": "HIGH", 292 | "vulnerableVersionRange": "< 1.3.2" 293 | }, 294 | "securityAdvisory": { 295 | "databaseId": 1657, 296 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLWZoamYtODN3Zy1yMmo5", 297 | "summary": "Prototype Pollution in mixin-deep", 298 | "severity": "HIGH", 299 | "description": "Versions of `mixin-deep` prior to 2.0.1 or 1.3.2 are vulnerable to Prototype Pollution. The `mixinDeep` function fails to validate which Object properties it updates. This allows attackers to modify the prototype of Object, causing the addition or modification of an existing property on all objects.\n\n\n\n\n## Recommendation\n\nIf you are using `mixin-deep` 2.x, upgrade to version 2.0.1 or later.\nIf you are using `mixin-deep` 1.x, upgrade to version 1.3.2 or later.", 300 | "ghsaId": "GHSA-fhjf-83wg-r2j9", 301 | "identifiers": [ 302 | { 303 | "type": "GHSA", 304 | "value": "GHSA-fhjf-83wg-r2j9" 305 | }, 306 | { 307 | "type": "CVE", 308 | "value": "CVE-2019-10746" 309 | } 310 | ], 311 | "permalink": "https://github.com/advisories/GHSA-fhjf-83wg-r2j9", 312 | "publishedAt": "2019-08-27T17:42:33Z" 313 | } 314 | }, 315 | { 316 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MTI=", 317 | "createdAt": "2020-09-05T14:05:43Z", 318 | "dismisser": null, 319 | "dismissedAt": null, 320 | "dismissReason": null, 321 | "vulnerableManifestFilename": "package-lock.json", 322 | "vulnerableRequirements": "= 2.0.0", 323 | "vulnerableManifestPath": "docs/package-lock.json", 324 | "securityVulnerability": { 325 | "package": { 326 | "ecosystem": "NPM", 327 | "name": "set-value" 328 | }, 329 | "severity": "HIGH", 330 | "vulnerableVersionRange": "< 2.0.1" 331 | }, 332 | "securityAdvisory": { 333 | "databaseId": 1658, 334 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTRnODgtZnBwci01M3Bw", 335 | "summary": "Prototype Pollution in set-value", 336 | "severity": "HIGH", 337 | "description": "Versions of `set-value` prior to 3.0.1 or 2.0.1 are vulnerable to Prototype Pollution. The `set` function fails to validate which Object properties it updates. This allows attackers to modify the prototype of Object, causing the addition or modification of an existing property on all objects.\n\n\n\n\n## Recommendation\n\nIf you are using `set-value` 3.x, upgrade to version 3.0.1 or later.\nIf you are using `set-value` 2.x, upgrade to version 2.0.1 or later.\n", 338 | "ghsaId": "GHSA-4g88-fppr-53pp", 339 | "identifiers": [ 340 | { 341 | "type": "GHSA", 342 | "value": "GHSA-4g88-fppr-53pp" 343 | }, 344 | { 345 | "type": "CVE", 346 | "value": "CVE-2019-10747" 347 | } 348 | ], 349 | "permalink": "https://github.com/advisories/GHSA-4g88-fppr-53pp", 350 | "publishedAt": "2019-08-27T17:43:33Z" 351 | } 352 | }, 353 | { 354 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MTc=", 355 | "createdAt": "2020-09-05T14:05:43Z", 356 | "dismisser": null, 357 | "dismissedAt": null, 358 | "dismissReason": null, 359 | "vulnerableManifestFilename": "package-lock.json", 360 | "vulnerableRequirements": "= 6.0.2", 361 | "vulnerableManifestPath": "docs/package-lock.json", 362 | "securityVulnerability": { 363 | "package": { 364 | "ecosystem": "NPM", 365 | "name": "kind-of" 366 | }, 367 | "severity": "LOW", 368 | "vulnerableVersionRange": ">= 6.0.0, < 6.0.3" 369 | }, 370 | "securityAdvisory": { 371 | "databaseId": 2015, 372 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTZjOGYtcXBoZy1xamdw", 373 | "summary": "Validation Bypass in kind-of", 374 | "severity": "LOW", 375 | "description": "Versions of `kind-of` 6.x prior to 6.0.3 are vulnerable to a Validation Bypass. A maliciously crafted object can alter the result of the type check, allowing attackers to bypass the type checking validation. \n\n\n## Recommendation\n\nUpgrade to versions 6.0.3 or later.", 376 | "ghsaId": "GHSA-6c8f-qphg-qjgp", 377 | "identifiers": [ 378 | { 379 | "type": "GHSA", 380 | "value": "GHSA-6c8f-qphg-qjgp" 381 | }, 382 | { 383 | "type": "CVE", 384 | "value": "CVE-2019-20149" 385 | } 386 | ], 387 | "permalink": "https://github.com/advisories/GHSA-6c8f-qphg-qjgp", 388 | "publishedAt": "2020-03-31T15:59:54Z" 389 | } 390 | }, 391 | { 392 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MjM=", 393 | "createdAt": "2020-09-05T14:05:43Z", 394 | "dismisser": null, 395 | "dismissedAt": null, 396 | "dismissReason": null, 397 | "vulnerableManifestFilename": "package-lock.json", 398 | "vulnerableRequirements": "= 1.2.0", 399 | "vulnerableManifestPath": "docs/package-lock.json", 400 | "securityVulnerability": { 401 | "package": { 402 | "ecosystem": "NPM", 403 | "name": "minimist" 404 | }, 405 | "severity": "LOW", 406 | "vulnerableVersionRange": ">= 1.0.0, < 1.2.3" 407 | }, 408 | "securityAdvisory": { 409 | "databaseId": 2026, 410 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXZoOTUtcm1nci02dzRt", 411 | "summary": "Prototype Pollution in minimist", 412 | "severity": "LOW", 413 | "description": "Affected versions of `minimist` are vulnerable to prototype pollution. Arguments are not properly sanitized, allowing an attacker to modify the prototype of `Object`, causing the addition or modification of an existing property that will exist on all objects. \nParsing the argument `--__proto__.y=Polluted` adds a `y` property with value `Polluted` to all objects. The argument `--__proto__=Polluted` raises and uncaught error and crashes the application. \nThis is exploitable if attackers have control over the arguments being passed to `minimist`.\n\n\n\n## Recommendation\n\nUpgrade to versions 0.2.1, 1.2.3 or later.", 414 | "ghsaId": "GHSA-vh95-rmgr-6w4m", 415 | "identifiers": [ 416 | { 417 | "type": "GHSA", 418 | "value": "GHSA-vh95-rmgr-6w4m" 419 | }, 420 | { 421 | "type": "CVE", 422 | "value": "CVE-2020-7598" 423 | } 424 | ], 425 | "permalink": "https://github.com/advisories/GHSA-vh95-rmgr-6w4m", 426 | "publishedAt": "2020-04-03T21:48:32Z" 427 | } 428 | }, 429 | { 430 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MzU=", 431 | "createdAt": "2020-09-05T14:05:43Z", 432 | "dismisser": null, 433 | "dismissedAt": null, 434 | "dismissReason": null, 435 | "vulnerableManifestFilename": "package-lock.json", 436 | "vulnerableRequirements": "= 4.17.11", 437 | "vulnerableManifestPath": "docs/package-lock.json", 438 | "securityVulnerability": { 439 | "package": { 440 | "ecosystem": "NPM", 441 | "name": "lodash" 442 | }, 443 | "severity": "LOW", 444 | "vulnerableVersionRange": "< 4.17.19" 445 | }, 446 | "securityAdvisory": { 447 | "databaseId": 2232, 448 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXA2bWMtbTQ2OC04M2d3", 449 | "summary": "Prototype Pollution in lodash", 450 | "severity": "LOW", 451 | "description": "Versions of lodash prior to 4.17.19 are vulnerable to Prototype Pollution. The function zipObjectDeep allows a malicious user to modify the prototype of Object if the property identifiers are user-supplied. Being affected by this issue requires zipping objects based on user-provided property arrays.\n\nThis vulnerability causes the addition or modification of an existing property that will exist on all objects and may lead to Denial of Service or Code Execution under specific circumstances.", 452 | "ghsaId": "GHSA-p6mc-m468-83gw", 453 | "identifiers": [ 454 | { 455 | "type": "GHSA", 456 | "value": "GHSA-p6mc-m468-83gw" 457 | }, 458 | { 459 | "type": "CVE", 460 | "value": "CVE-2020-8203" 461 | } 462 | ], 463 | "permalink": "https://github.com/advisories/GHSA-p6mc-m468-83gw", 464 | "publishedAt": "2020-07-15T19:15:48Z" 465 | } 466 | }, 467 | { 468 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4MzY=", 469 | "createdAt": "2020-09-05T14:05:44Z", 470 | "dismisser": null, 471 | "dismissedAt": null, 472 | "dismissReason": null, 473 | "vulnerableManifestFilename": "package-lock.json", 474 | "vulnerableRequirements": "= 4.11.0", 475 | "vulnerableManifestPath": "docs/package-lock.json", 476 | "securityVulnerability": { 477 | "package": { 478 | "ecosystem": "NPM", 479 | "name": "node-sass" 480 | }, 481 | "severity": "LOW", 482 | "vulnerableVersionRange": ">= 3.3.0, < 4.13.1" 483 | }, 484 | "securityAdvisory": { 485 | "databaseId": 2655, 486 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTl2NjItMjRjci01OGN4", 487 | "summary": "Denial of Service in node-sass", 488 | "severity": "LOW", 489 | "description": "Affected versions of `node-sass` are vulnerable to Denial of Service (DoS). Crafted objects passed to the `renderSync` function may trigger C++ assertions in `CustomImporterBridge::get_importer_entry` and `CustomImporterBridge::post_process_return_value` that crash the Node process. This may allow attackers to crash the system's running Node process and lead to Denial of Service.\n\n\n## Recommendation\n\nUpgrade to version 4.13.1 or later", 490 | "ghsaId": "GHSA-9v62-24cr-58cx", 491 | "identifiers": [ 492 | { 493 | "type": "GHSA", 494 | "value": "GHSA-9v62-24cr-58cx" 495 | } 496 | ], 497 | "permalink": "https://github.com/advisories/GHSA-9v62-24cr-58cx", 498 | "publishedAt": "2020-09-03T15:27:25Z" 499 | } 500 | }, 501 | { 502 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4Mzc=", 503 | "createdAt": "2020-09-05T14:05:44Z", 504 | "dismisser": null, 505 | "dismissedAt": null, 506 | "dismissReason": null, 507 | "vulnerableManifestFilename": "package-lock.json", 508 | "vulnerableRequirements": "= 4.6.1", 509 | "vulnerableManifestPath": "docs/package-lock.json", 510 | "securityVulnerability": { 511 | "package": { 512 | "ecosystem": "NPM", 513 | "name": "lodash.mergewith" 514 | }, 515 | "severity": "HIGH", 516 | "vulnerableVersionRange": "< 4.6.2" 517 | }, 518 | "securityAdvisory": { 519 | "databaseId": 2726, 520 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTc3OWYtd2d4Zy1xcjhm", 521 | "summary": "Prototype Pollution in lodash.mergewith", 522 | "severity": "HIGH", 523 | "description": "Versions of `lodash.mergewith` before 4.6.2 are vulnerable to prototype pollution. The function `mergeWith` may allow a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.\n\n\n\n\n## Recommendation\n\nUpdate to version 4.6.2 or later.", 524 | "ghsaId": "GHSA-779f-wgxg-qr8f", 525 | "identifiers": [ 526 | { 527 | "type": "GHSA", 528 | "value": "GHSA-779f-wgxg-qr8f" 529 | } 530 | ], 531 | "permalink": "https://github.com/advisories/GHSA-779f-wgxg-qr8f", 532 | "publishedAt": "2020-09-03T18:10:22Z" 533 | } 534 | }, 535 | { 536 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4Mzg=", 537 | "createdAt": "2020-09-05T14:05:44Z", 538 | "dismisser": null, 539 | "dismissedAt": null, 540 | "dismissReason": null, 541 | "vulnerableManifestFilename": "package-lock.json", 542 | "vulnerableRequirements": "= 1.15.2", 543 | "vulnerableManifestPath": "docs/package-lock.json", 544 | "securityVulnerability": { 545 | "package": { 546 | "ecosystem": "NPM", 547 | "name": "http-proxy" 548 | }, 549 | "severity": "HIGH", 550 | "vulnerableVersionRange": "< 1.18.1" 551 | }, 552 | "securityAdvisory": { 553 | "databaseId": 3040, 554 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLTZ4MzMtcHc3cC1obXBx", 555 | "summary": "Denial of Service in http-proxy", 556 | "severity": "HIGH", 557 | "description": "Versions of `http-proxy` prior to 1.18.1 are vulnerable to Denial of Service. An HTTP request with a long body triggers an `ERR_HTTP_HEADERS_SENT` unhandled exception that crashes the proxy server. This is only possible when the proxy server sets headers in the proxy request using the `proxyReq.setHeader` function. \n\nFor a proxy server running on `http://localhost:3000`, the following curl request triggers the unhandled exception: \n```curl -XPOST http://localhost:3000 -d \"$(python -c 'print(\"x\"*1025)')\"```\n\n\n## Recommendation\n\nUpgrade to version 1.18.1 or later", 558 | "ghsaId": "GHSA-6x33-pw7p-hmpq", 559 | "identifiers": [ 560 | { 561 | "type": "GHSA", 562 | "value": "GHSA-6x33-pw7p-hmpq" 563 | } 564 | ], 565 | "permalink": "https://github.com/advisories/GHSA-6x33-pw7p-hmpq", 566 | "publishedAt": "2020-09-04T17:59:49Z" 567 | } 568 | }, 569 | { 570 | "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQzNzM0MjI4Mzk=", 571 | "createdAt": "2020-09-05T14:05:44Z", 572 | "dismisser": null, 573 | "dismissedAt": null, 574 | "dismissReason": null, 575 | "vulnerableManifestFilename": "package-lock.json", 576 | "vulnerableRequirements": "= 4.2.1", 577 | "vulnerableManifestPath": "docs/package-lock.json", 578 | "securityVulnerability": { 579 | "package": { 580 | "ecosystem": "NPM", 581 | "name": "yargs-parser" 582 | }, 583 | "severity": "LOW", 584 | "vulnerableVersionRange": "< 13.1.2" 585 | }, 586 | "securityAdvisory": { 587 | "databaseId": 3048, 588 | "id": "MDE2OlNlY3VyaXR5QWR2aXNvcnlHSFNBLXA5cGMtMjk5cC12eGdw", 589 | "summary": "Prototype Pollution in yargs-parser", 590 | "severity": "LOW", 591 | "description": "Affected versions of `yargs-parser` are vulnerable to prototype pollution. Arguments are not properly sanitized, allowing an attacker to modify the prototype of `Object`, causing the addition or modification of an existing property that will exist on all objects. \nParsing the argument `--foo.__proto__.bar baz'` adds a `bar` property with value `baz` to all objects. This is only exploitable if attackers have control over the arguments being passed to `yargs-parser`.\n\n\n\n## Recommendation\n\nUpgrade to versions 13.1.2, 15.0.1, 18.1.1 or later.", 592 | "ghsaId": "GHSA-p9pc-299p-vxgp", 593 | "identifiers": [ 594 | { 595 | "type": "GHSA", 596 | "value": "GHSA-p9pc-299p-vxgp" 597 | } 598 | ], 599 | "permalink": "https://github.com/advisories/GHSA-p9pc-299p-vxgp", 600 | "publishedAt": "2020-09-04T18:00:54Z" 601 | } 602 | } 603 | ] 604 | } 605 | } 606 | } 607 | } -------------------------------------------------------------------------------- /samples/sarif/java/basic/java.sarif: -------------------------------------------------------------------------------- 1 | { 2 | "$schema" : "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", 3 | "version" : "2.1.0", 4 | "runs" : [ { 5 | "tool" : { 6 | "driver" : { 7 | "name" : "CodeQL", 8 | "organization" : "GitHub", 9 | "semanticVersion" : "2.2.5", 10 | "rules" : [ { 11 | "id" : "java/unsafe-deserialization", 12 | "name" : "java/unsafe-deserialization", 13 | "shortDescription" : { 14 | "text" : "Deserialization of user-controlled data" 15 | }, 16 | "fullDescription" : { 17 | "text" : "Deserializing user-controlled data may allow attackers to execute arbitrary code." 18 | }, 19 | "defaultConfiguration" : { 20 | "level" : "error" 21 | }, 22 | "properties" : { 23 | "tags" : [ "security", "external/cwe/cwe-502" ], 24 | "kind" : "path-problem", 25 | "precision" : "high", 26 | "name" : "Deserialization of user-controlled data", 27 | "description" : "Deserializing user-controlled data may allow attackers to\n execute arbitrary code.", 28 | "id" : "java/unsafe-deserialization", 29 | "problem.severity" : "error" 30 | } 31 | }, { 32 | "id" : "java/ldap-injection", 33 | "name" : "java/ldap-injection", 34 | "shortDescription" : { 35 | "text" : "LDAP query built from user-controlled sources" 36 | }, 37 | "fullDescription" : { 38 | "text" : "Building an LDAP query from user-controlled sources is vulnerable to insertion of malicious LDAP code by the user." 39 | }, 40 | "defaultConfiguration" : { 41 | "level" : "error" 42 | }, 43 | "properties" : { 44 | "tags" : [ "security", "external/cwe/cwe-090" ], 45 | "kind" : "path-problem", 46 | "precision" : "high", 47 | "name" : "LDAP query built from user-controlled sources", 48 | "description" : "Building an LDAP query from user-controlled sources is vulnerable to insertion of\n malicious LDAP code by the user.", 49 | "id" : "java/ldap-injection", 50 | "problem.severity" : "error" 51 | } 52 | }, { 53 | "id" : "java/xss", 54 | "name" : "java/xss", 55 | "shortDescription" : { 56 | "text" : "Cross-site scripting" 57 | }, 58 | "fullDescription" : { 59 | "text" : "Writing user input directly to a web page allows for a cross-site scripting vulnerability." 60 | }, 61 | "defaultConfiguration" : { 62 | "level" : "error" 63 | }, 64 | "properties" : { 65 | "tags" : [ "security", "external/cwe/cwe-079" ], 66 | "kind" : "path-problem", 67 | "precision" : "high", 68 | "name" : "Cross-site scripting", 69 | "description" : "Writing user input directly to a web page\n allows for a cross-site scripting vulnerability.", 70 | "id" : "java/xss", 71 | "problem.severity" : "error" 72 | } 73 | }, { 74 | "id" : "java/insecure-cookie", 75 | "name" : "java/insecure-cookie", 76 | "shortDescription" : { 77 | "text" : "Failure to use secure cookies" 78 | }, 79 | "fullDescription" : { 80 | "text" : "Insecure cookies may be sent in cleartext, which makes them vulnerable to interception." 81 | }, 82 | "defaultConfiguration" : { 83 | "level" : "error" 84 | }, 85 | "properties" : { 86 | "tags" : [ "security", "external/cwe/cwe-614" ], 87 | "kind" : "problem", 88 | "precision" : "high", 89 | "name" : "Failure to use secure cookies", 90 | "description" : "Insecure cookies may be sent in cleartext, which makes them vulnerable to\n interception.", 91 | "id" : "java/insecure-cookie", 92 | "problem.severity" : "error" 93 | } 94 | }, { 95 | "id" : "java/maven/non-https-url", 96 | "name" : "java/maven/non-https-url", 97 | "shortDescription" : { 98 | "text" : "Failure to use HTTPS or SFTP URL in Maven artifact upload/download" 99 | }, 100 | "fullDescription" : { 101 | "text" : "Non-HTTPS connections can be intercepted by third parties." 102 | }, 103 | "defaultConfiguration" : { 104 | "level" : "error" 105 | }, 106 | "properties" : { 107 | "tags" : [ "security", "external/cwe/cwe-300", "external/cwe/cwe-319", "external/cwe/cwe-494", "external/cwe/cwe-829" ], 108 | "kind" : "problem", 109 | "precision" : "very-high", 110 | "name" : "Failure to use HTTPS or SFTP URL in Maven artifact upload/download", 111 | "description" : "Non-HTTPS connections can be intercepted by third parties.", 112 | "id" : "java/maven/non-https-url", 113 | "problem.severity" : "error" 114 | } 115 | }, { 116 | "id" : "java/tainted-format-string", 117 | "name" : "java/tainted-format-string", 118 | "shortDescription" : { 119 | "text" : "Use of externally-controlled format string" 120 | }, 121 | "fullDescription" : { 122 | "text" : "Using external input in format strings can lead to exceptions or information leaks." 123 | }, 124 | "defaultConfiguration" : { 125 | "level" : "error" 126 | }, 127 | "properties" : { 128 | "tags" : [ "security", "external/cwe/cwe-134" ], 129 | "kind" : "path-problem", 130 | "precision" : "high", 131 | "name" : "Use of externally-controlled format string", 132 | "description" : "Using external input in format strings can lead to exceptions or information leaks.", 133 | "id" : "java/tainted-format-string", 134 | "problem.severity" : "error" 135 | } 136 | }, { 137 | "id" : "java/sql-injection", 138 | "name" : "java/sql-injection", 139 | "shortDescription" : { 140 | "text" : "Query built from user-controlled sources" 141 | }, 142 | "fullDescription" : { 143 | "text" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of malicious code by the user." 144 | }, 145 | "defaultConfiguration" : { 146 | "level" : "error" 147 | }, 148 | "properties" : { 149 | "tags" : [ "security", "external/cwe/cwe-089" ], 150 | "kind" : "path-problem", 151 | "precision" : "high", 152 | "name" : "Query built from user-controlled sources", 153 | "description" : "Building a SQL or Java Persistence query from user-controlled sources is vulnerable to insertion of\n malicious code by the user.", 154 | "id" : "java/sql-injection", 155 | "problem.severity" : "error" 156 | } 157 | }, { 158 | "id" : "java/concatenated-sql-query", 159 | "name" : "java/concatenated-sql-query", 160 | "shortDescription" : { 161 | "text" : "Query built without neutralizing special characters" 162 | }, 163 | "fullDescription" : { 164 | "text" : "Building a SQL or Java Persistence query without escaping or otherwise neutralizing any special characters is vulnerable to insertion of malicious code." 165 | }, 166 | "defaultConfiguration" : { 167 | "level" : "error" 168 | }, 169 | "properties" : { 170 | "tags" : [ "security", "external/cwe/cwe-089" ], 171 | "kind" : "problem", 172 | "precision" : "high", 173 | "name" : "Query built without neutralizing special characters", 174 | "description" : "Building a SQL or Java Persistence query without escaping or otherwise neutralizing any special\n characters is vulnerable to insertion of malicious code.", 175 | "id" : "java/concatenated-sql-query", 176 | "problem.severity" : "error" 177 | } 178 | }, { 179 | "id" : "java/tainted-numeric-cast", 180 | "name" : "java/tainted-numeric-cast", 181 | "shortDescription" : { 182 | "text" : "User-controlled data in numeric cast" 183 | }, 184 | "fullDescription" : { 185 | "text" : "Casting user-controlled numeric data to a narrower type without validation can cause unexpected truncation." 186 | }, 187 | "defaultConfiguration" : { 188 | "level" : "error" 189 | }, 190 | "properties" : { 191 | "tags" : [ "security", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 192 | "kind" : "path-problem", 193 | "precision" : "high", 194 | "name" : "User-controlled data in numeric cast", 195 | "description" : "Casting user-controlled numeric data to a narrower type without validation\n can cause unexpected truncation.", 196 | "id" : "java/tainted-numeric-cast", 197 | "problem.severity" : "error" 198 | } 199 | }, { 200 | "id" : "java/tainted-permissions-check", 201 | "name" : "java/tainted-permissions-check", 202 | "shortDescription" : { 203 | "text" : "User-controlled data used in permissions check" 204 | }, 205 | "fullDescription" : { 206 | "text" : "Using user-controlled data in a permissions check may result in inappropriate permissions being granted." 207 | }, 208 | "defaultConfiguration" : { 209 | "level" : "error" 210 | }, 211 | "properties" : { 212 | "tags" : [ "security", "external/cwe/cwe-807", "external/cwe/cwe-290" ], 213 | "kind" : "path-problem", 214 | "precision" : "high", 215 | "name" : "User-controlled data used in permissions check", 216 | "description" : "Using user-controlled data in a permissions check may result in inappropriate\n permissions being granted.", 217 | "id" : "java/tainted-permissions-check", 218 | "problem.severity" : "error" 219 | } 220 | }, { 221 | "id" : "java/spring-disabled-csrf-protection", 222 | "name" : "java/spring-disabled-csrf-protection", 223 | "shortDescription" : { 224 | "text" : "Disabled Spring CSRF protection" 225 | }, 226 | "fullDescription" : { 227 | "text" : "Disabling CSRF protection makes the application vulnerable to a Cross-Site Request Forgery (CSRF) attack." 228 | }, 229 | "defaultConfiguration" : { 230 | "level" : "error" 231 | }, 232 | "properties" : { 233 | "tags" : [ "security", "external/cwe/cwe-352" ], 234 | "kind" : "problem", 235 | "precision" : "high", 236 | "name" : "Disabled Spring CSRF protection", 237 | "description" : "Disabling CSRF protection makes the application vulnerable to\n a Cross-Site Request Forgery (CSRF) attack.", 238 | "id" : "java/spring-disabled-csrf-protection", 239 | "problem.severity" : "error" 240 | } 241 | }, { 242 | "id" : "java/cleartext-storage-in-cookie", 243 | "name" : "java/cleartext-storage-in-cookie", 244 | "shortDescription" : { 245 | "text" : "Cleartext storage of sensitive information in cookie" 246 | }, 247 | "fullDescription" : { 248 | "text" : "Storing sensitive information in cleartext can expose it to an attacker." 249 | }, 250 | "defaultConfiguration" : { 251 | "level" : "error" 252 | }, 253 | "properties" : { 254 | "tags" : [ "security", "external/cwe/cwe-315" ], 255 | "kind" : "problem", 256 | "precision" : "high", 257 | "name" : "Cleartext storage of sensitive information in cookie", 258 | "description" : "Storing sensitive information in cleartext can expose it to an attacker.", 259 | "id" : "java/cleartext-storage-in-cookie", 260 | "problem.severity" : "error" 261 | } 262 | }, { 263 | "id" : "java/unvalidated-url-redirection", 264 | "name" : "java/unvalidated-url-redirection", 265 | "shortDescription" : { 266 | "text" : "URL redirection from remote source" 267 | }, 268 | "fullDescription" : { 269 | "text" : "URL redirection based on unvalidated user-input may cause redirection to malicious web sites." 270 | }, 271 | "defaultConfiguration" : { 272 | "level" : "error" 273 | }, 274 | "properties" : { 275 | "tags" : [ "security", "external/cwe/cwe-601" ], 276 | "kind" : "path-problem", 277 | "precision" : "high", 278 | "name" : "URL redirection from remote source", 279 | "description" : "URL redirection based on unvalidated user-input\n may cause redirection to malicious web sites.", 280 | "id" : "java/unvalidated-url-redirection", 281 | "problem.severity" : "error" 282 | } 283 | }, { 284 | "id" : "java/stack-trace-exposure", 285 | "name" : "java/stack-trace-exposure", 286 | "shortDescription" : { 287 | "text" : "Information exposure through a stack trace" 288 | }, 289 | "fullDescription" : { 290 | "text" : "Information from a stack trace propagates to an external user. Stack traces can unintentionally reveal implementation details that are useful to an attacker for developing a subsequent exploit." 291 | }, 292 | "defaultConfiguration" : { 293 | "level" : "error" 294 | }, 295 | "properties" : { 296 | "tags" : [ "security", "external/cwe/cwe-209", "external/cwe/cwe-497" ], 297 | "kind" : "problem", 298 | "precision" : "high", 299 | "name" : "Information exposure through a stack trace", 300 | "description" : "Information from a stack trace propagates to an external user.\n Stack traces can unintentionally reveal implementation details\n that are useful to an attacker for developing a subsequent exploit.", 301 | "id" : "java/stack-trace-exposure", 302 | "problem.severity" : "error" 303 | } 304 | }, { 305 | "id" : "java/netty-http-response-splitting", 306 | "name" : "java/netty-http-response-splitting", 307 | "shortDescription" : { 308 | "text" : "Disabled Netty HTTP header validation" 309 | }, 310 | "fullDescription" : { 311 | "text" : "Disabling HTTP header validation makes code vulnerable to attack by header splitting if user input is written directly to an HTTP header." 312 | }, 313 | "defaultConfiguration" : { 314 | "level" : "error" 315 | }, 316 | "properties" : { 317 | "tags" : [ "security", "external/cwe/cwe-113" ], 318 | "kind" : "problem", 319 | "precision" : "high", 320 | "name" : "Disabled Netty HTTP header validation", 321 | "description" : "Disabling HTTP header validation makes code vulnerable to\n attack by header splitting if user input is written directly to\n an HTTP header.", 322 | "id" : "java/netty-http-response-splitting", 323 | "problem.severity" : "error" 324 | } 325 | }, { 326 | "id" : "java/http-response-splitting", 327 | "name" : "java/http-response-splitting", 328 | "shortDescription" : { 329 | "text" : "HTTP response splitting" 330 | }, 331 | "fullDescription" : { 332 | "text" : "Writing user input directly to an HTTP header makes code vulnerable to attack by header splitting." 333 | }, 334 | "defaultConfiguration" : { 335 | "level" : "error" 336 | }, 337 | "properties" : { 338 | "tags" : [ "security", "external/cwe/cwe-113" ], 339 | "kind" : "path-problem", 340 | "precision" : "high", 341 | "name" : "HTTP response splitting", 342 | "description" : "Writing user input directly to an HTTP header\n makes code vulnerable to attack by header splitting.", 343 | "id" : "java/http-response-splitting", 344 | "problem.severity" : "error" 345 | } 346 | }, { 347 | "id" : "java/zipslip", 348 | "name" : "java/zipslip", 349 | "shortDescription" : { 350 | "text" : "Arbitrary file write during archive extraction (\"Zip Slip\")" 351 | }, 352 | "fullDescription" : { 353 | "text" : "Extracting files from a malicious archive without validating that the destination file path is within the destination directory can cause files outside the destination directory to be overwritten." 354 | }, 355 | "defaultConfiguration" : { 356 | "level" : "error" 357 | }, 358 | "properties" : { 359 | "tags" : [ "security", "external/cwe/cwe-022" ], 360 | "kind" : "path-problem", 361 | "precision" : "high", 362 | "name" : "Arbitrary file write during archive extraction (\"Zip Slip\")", 363 | "description" : "Extracting files from a malicious archive without validating that the\n destination file path is within the destination directory can cause files outside\n the destination directory to be overwritten.", 364 | "id" : "java/zipslip", 365 | "problem.severity" : "error" 366 | } 367 | }, { 368 | "id" : "java/path-injection", 369 | "name" : "java/path-injection", 370 | "shortDescription" : { 371 | "text" : "Uncontrolled data used in path expression" 372 | }, 373 | "fullDescription" : { 374 | "text" : "Accessing paths influenced by users can allow an attacker to access unexpected resources." 375 | }, 376 | "defaultConfiguration" : { 377 | "level" : "error" 378 | }, 379 | "properties" : { 380 | "tags" : [ "security", "external/cwe/cwe-022", "external/cwe/cwe-023", "external/cwe/cwe-036", "external/cwe/cwe-073" ], 381 | "kind" : "path-problem", 382 | "precision" : "high", 383 | "name" : "Uncontrolled data used in path expression", 384 | "description" : "Accessing paths influenced by users can allow an attacker to access unexpected resources.", 385 | "id" : "java/path-injection", 386 | "problem.severity" : "error" 387 | } 388 | }, { 389 | "id" : "java/predictable-seed", 390 | "name" : "java/predictable-seed", 391 | "shortDescription" : { 392 | "text" : "Use of a predictable seed in a secure random number generator" 393 | }, 394 | "fullDescription" : { 395 | "text" : "Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it." 396 | }, 397 | "defaultConfiguration" : { 398 | "level" : "error" 399 | }, 400 | "properties" : { 401 | "tags" : [ "security" ], 402 | "kind" : "problem", 403 | "precision" : "high", 404 | "name" : "Use of a predictable seed in a secure random number generator", 405 | "description" : "Using a predictable seed in a pseudo-random number generator can lead to predictability of the numbers generated by it.", 406 | "id" : "java/predictable-seed", 407 | "problem.severity" : "error" 408 | } 409 | }, { 410 | "id" : "java/command-line-injection", 411 | "name" : "java/command-line-injection", 412 | "shortDescription" : { 413 | "text" : "Uncontrolled command line" 414 | }, 415 | "fullDescription" : { 416 | "text" : "Using externally controlled strings in a command line is vulnerable to malicious changes in the strings." 417 | }, 418 | "defaultConfiguration" : { 419 | "level" : "error" 420 | }, 421 | "properties" : { 422 | "tags" : [ "security", "external/cwe/cwe-078", "external/cwe/cwe-088" ], 423 | "kind" : "path-problem", 424 | "precision" : "high", 425 | "name" : "Uncontrolled command line", 426 | "description" : "Using externally controlled strings in a command line is vulnerable to malicious\n changes in the strings.", 427 | "id" : "java/command-line-injection", 428 | "problem.severity" : "error" 429 | } 430 | }, { 431 | "id" : "java/concatenated-command-line", 432 | "name" : "java/concatenated-command-line", 433 | "shortDescription" : { 434 | "text" : "Building a command line with string concatenation" 435 | }, 436 | "fullDescription" : { 437 | "text" : "Using concatenated strings in a command line is vulnerable to malicious insertion of special characters in the strings." 438 | }, 439 | "defaultConfiguration" : { 440 | "level" : "error" 441 | }, 442 | "properties" : { 443 | "tags" : [ "security", "external/cwe/cwe-078", "external/cwe/cwe-088" ], 444 | "kind" : "problem", 445 | "precision" : "high", 446 | "name" : "Building a command line with string concatenation", 447 | "description" : "Using concatenated strings in a command line is vulnerable to malicious\n insertion of special characters in the strings.", 448 | "id" : "java/concatenated-command-line", 449 | "problem.severity" : "error" 450 | } 451 | }, { 452 | "id" : "java/world-writable-file-read", 453 | "name" : "java/world-writable-file-read", 454 | "shortDescription" : { 455 | "text" : "Reading from a world writable file" 456 | }, 457 | "fullDescription" : { 458 | "text" : "Reading from a file which is set as world writable is dangerous because the file may be modified or removed by external actors." 459 | }, 460 | "defaultConfiguration" : { 461 | "level" : "error" 462 | }, 463 | "properties" : { 464 | "tags" : [ "security", "external/cwe/cwe-732" ], 465 | "kind" : "problem", 466 | "precision" : "high", 467 | "name" : "Reading from a world writable file", 468 | "description" : "Reading from a file which is set as world writable is dangerous because\n the file may be modified or removed by external actors.", 469 | "id" : "java/world-writable-file-read", 470 | "problem.severity" : "error" 471 | } 472 | }, { 473 | "id" : "java/xxe", 474 | "name" : "java/xxe", 475 | "shortDescription" : { 476 | "text" : "Resolving XML external entity in user-controlled data" 477 | }, 478 | "fullDescription" : { 479 | "text" : "Parsing user-controlled XML documents and allowing expansion of external entity references may lead to disclosure of confidential data or denial of service." 480 | }, 481 | "defaultConfiguration" : { 482 | "level" : "error" 483 | }, 484 | "properties" : { 485 | "tags" : [ "security", "external/cwe/cwe-611" ], 486 | "kind" : "path-problem", 487 | "precision" : "high", 488 | "name" : "Resolving XML external entity in user-controlled data", 489 | "description" : "Parsing user-controlled XML documents and allowing expansion of external entity\n references may lead to disclosure of confidential data or denial of service.", 490 | "id" : "java/xxe", 491 | "problem.severity" : "error" 492 | } 493 | }, { 494 | "id" : "java/implicit-cast-in-compound-assignment", 495 | "name" : "java/implicit-cast-in-compound-assignment", 496 | "shortDescription" : { 497 | "text" : "Implicit narrowing conversion in compound assignment" 498 | }, 499 | "fullDescription" : { 500 | "text" : "Compound assignment statements (for example 'intvar += longvar') that implicitly cast a value of a wider type to a narrower type may result in information loss and numeric errors such as overflows." 501 | }, 502 | "defaultConfiguration" : { }, 503 | "properties" : { 504 | "tags" : [ "reliability", "security", "external/cwe/cwe-190", "external/cwe/cwe-192", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 505 | "kind" : "problem", 506 | "precision" : "very-high", 507 | "name" : "Implicit narrowing conversion in compound assignment", 508 | "description" : "Compound assignment statements (for example 'intvar += longvar') that implicitly\n cast a value of a wider type to a narrower type may result in information loss and\n numeric errors such as overflows.", 509 | "id" : "java/implicit-cast-in-compound-assignment", 510 | "problem.severity" : "warning" 511 | } 512 | }, { 513 | "id" : "java/integer-multiplication-cast-to-long", 514 | "name" : "java/integer-multiplication-cast-to-long", 515 | "shortDescription" : { 516 | "text" : "Result of multiplication cast to wider type" 517 | }, 518 | "fullDescription" : { 519 | "text" : "Casting the result of a multiplication to a wider type instead of casting before the multiplication may cause overflow." 520 | }, 521 | "defaultConfiguration" : { }, 522 | "properties" : { 523 | "tags" : [ "reliability", "security", "correctness", "types", "external/cwe/cwe-190", "external/cwe/cwe-192", "external/cwe/cwe-197", "external/cwe/cwe-681" ], 524 | "kind" : "problem", 525 | "precision" : "very-high", 526 | "name" : "Result of multiplication cast to wider type", 527 | "description" : "Casting the result of a multiplication to a wider type instead of casting\n before the multiplication may cause overflow.", 528 | "id" : "java/integer-multiplication-cast-to-long", 529 | "problem.severity" : "warning" 530 | } 531 | } ] 532 | } 533 | }, 534 | "results" : [ ], 535 | "columnKind" : "utf16CodeUnits", 536 | "properties" : { 537 | "semmle.formatSpecifier" : "sarif-latest" 538 | } 539 | } ] 540 | } -------------------------------------------------------------------------------- /src/DataCollector.ts: -------------------------------------------------------------------------------- 1 | import {Octokit} from "@octokit/rest"; 2 | import GitHubCodeScanning from './codeScanning/GitHubCodeScanning'; 3 | import GitHubDependencies from './dependencies/GitHubDependencies'; 4 | import SarifReportFinder from './sarif/SarifReportFinder'; 5 | import ReportData from './templating/ReportData'; 6 | import { CollectedData } from './templating/ReportTypes'; 7 | 8 | 9 | type Repo = { 10 | owner: string, 11 | repo: string 12 | } 13 | 14 | export default class DataCollector { 15 | 16 | private readonly repo: Repo 17 | 18 | private readonly octokit 19 | 20 | constructor(octokit: Octokit, repo: string) { 21 | if (!octokit) { 22 | throw new Error('A GitHub Octokit client needs to be provided'); 23 | } 24 | this.octokit = octokit; 25 | 26 | if (!repo) { 27 | throw new Error('A GitHub repository must be provided'); 28 | } 29 | 30 | const parts = repo.split('/') 31 | this.repo = { 32 | owner: parts[0], 33 | repo: parts[1] 34 | } 35 | } 36 | 37 | getPayload(sarifReportDir: string): Promise { 38 | const ghDeps = new GitHubDependencies(this.octokit) 39 | , codeScanning = new GitHubCodeScanning(this.octokit) 40 | , sarifFinder = new SarifReportFinder(sarifReportDir) 41 | ; 42 | 43 | return Promise.all([ 44 | sarifFinder.getSarifFiles(), 45 | ghDeps.getAllDependencies(this.repo), 46 | ghDeps.getAllVulnerabilities(this.repo), 47 | codeScanning.getOpenCodeScanningAlerts(this.repo), 48 | codeScanning.getClosedCodeScanningAlerts(this.repo), 49 | ]).then(results => { 50 | 51 | const data: CollectedData = { 52 | github: this.repo, 53 | sarifReports: results[0], 54 | dependencies: results[1], 55 | vulnerabilities: results[2], 56 | codeScanningOpen: results[3], 57 | codeScanningClosed: results[4], 58 | }; 59 | 60 | return new ReportData(data); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ReportGenerator.test.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/rest'; 2 | import { expect } from 'chai'; 3 | import ReportGenerator from './ReportGenerator'; 4 | import { getGitHubToken, getSampleSarifDirectory, getTestDirectoryFilePath } from './testUtils'; 5 | 6 | const TOKEN: string = getGitHubToken(); 7 | 8 | const SIMPLE_TEST_REPOSITORY = { 9 | repository: 'octodemo/ghas-reporting', 10 | sarifReportDir: getSampleSarifDirectory('java', 'detailed') 11 | } 12 | 13 | const PM_AS_JAVA = { 14 | repository: 'peter-murray/advanced-security-java', 15 | sarifReportDir: getSampleSarifDirectory('peter-murray', 'advanced-security-java') 16 | } 17 | 18 | describe('ReportGenerator', function () { 19 | 20 | this.timeout(10 * 1000); 21 | 22 | [SIMPLE_TEST_REPOSITORY, PM_AS_JAVA].forEach(config => { 23 | it(`should generate a report for ${config.repository}`, async () => { 24 | const generatorConfig = { 25 | octokit: new Octokit({auth: TOKEN}), 26 | repository: config.repository, 27 | 28 | sarifReportDirectory: config.sarifReportDir, 29 | outputDirectory: getTestDirectoryFilePath(config.repository), 30 | 31 | templating: { 32 | name: 'summary' 33 | } 34 | } 35 | 36 | const generator = new ReportGenerator(generatorConfig); 37 | const file = await generator.run(); 38 | expect(file).to.contain(generatorConfig.outputDirectory); 39 | //TODO need to store an expected result 40 | }); 41 | }) 42 | 43 | 44 | }); -------------------------------------------------------------------------------- /src/ReportGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/rest'; 2 | import DataCollector from './DataCollector'; 3 | import Template from './templating/Template'; 4 | import { createPDF } from './pdf/pdfWriter'; 5 | import * as path from 'path'; 6 | 7 | import { mkdirP } from '@actions/io'; 8 | 9 | export type ReportGeneratorConfig = { 10 | repository: string, 11 | octokit: Octokit, 12 | 13 | sarifReportDirectory: string, 14 | outputDirectory: string, 15 | 16 | templating: { 17 | directory?: string, 18 | name: string, 19 | } 20 | } 21 | 22 | export default class ReportGenerator { 23 | 24 | private readonly config: ReportGeneratorConfig; 25 | 26 | constructor(config: ReportGeneratorConfig) { 27 | this.config = config; 28 | } 29 | 30 | run(): Promise { 31 | const config = this.config; 32 | const collector = new DataCollector(config.octokit, config.repository); 33 | 34 | return collector.getPayload(config.sarifReportDirectory) 35 | .then(reportData => { 36 | const reportTemplate = new Template(config.templating.directory); 37 | return reportTemplate.render(reportData.getJSONPayload(), config.templating.name); 38 | }) 39 | .then(html => { 40 | return mkdirP(config.outputDirectory) 41 | .then(() => { 42 | return createPDF(html, path.join(config.outputDirectory, 'summary.pdf')); 43 | }); 44 | }); 45 | } 46 | } -------------------------------------------------------------------------------- /src/codeScanning/CodeScanningAlert.ts: -------------------------------------------------------------------------------- 1 | export type Rule = { 2 | id: string, 3 | severity: string, 4 | description: string 5 | } 6 | 7 | export type CodeScanningData = { 8 | number: number; 9 | /** 10 | * The time that the alert was created in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`. 11 | */ 12 | created_at: string; 13 | /** 14 | * The REST API URL of the alert resource. 15 | */ 16 | url: string; 17 | /** 18 | * The GitHub URL of the alert resource. 19 | */ 20 | html_url: string; 21 | /** 22 | * State of a code scanning alert. 23 | */ 24 | state: "open" | "dismissed" | "fixed"; 25 | dismissed_by: { 26 | login?: string; 27 | id?: number; 28 | node_id?: string; 29 | avatar_url?: string; 30 | gravatar_id?: string; 31 | url?: string; 32 | html_url?: string; 33 | followers_url?: string; 34 | following_url?: string; 35 | gists_url?: string; 36 | starred_url?: string; 37 | subscriptions_url?: string; 38 | organizations_url?: string; 39 | repos_url?: string; 40 | events_url?: string; 41 | received_events_url?: string; 42 | type?: string; 43 | site_admin?: boolean; 44 | [k: string]: unknown; 45 | } | null; 46 | /** 47 | * The time that the alert was dismissed in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ`. 48 | */ 49 | dismissed_at: string; 50 | /** 51 | * **Required when the state is dismissed.** The reason for dismissing or closing the alert. Can be one of: `false positive`, `won't fix`, and `used in tests`. 52 | */ 53 | dismissed_reason: ("false positive" | "won't fix" | "used in tests") | null; 54 | rule: { 55 | /** 56 | * A unique identifier for the rule used to detect the alert. 57 | */ 58 | id: string; 59 | /** 60 | * The severity of the alert. 61 | */ 62 | severity: "none" | "note" | "warning" | "error"; 63 | /** 64 | * A short description of the rule used to detect the alert. 65 | */ 66 | description: string; 67 | }; 68 | tool: { 69 | /** 70 | * The name of the tool used to generate the code scanning analysis alert. 71 | */ 72 | name: string; 73 | /** 74 | * The version of the tool used to detect the alert. 75 | */ 76 | version: string; 77 | }; 78 | } 79 | 80 | export type AlertDismissal = { 81 | at: string, 82 | reason: 'false positive' | 'won\'t fix' | 'used in tests' | null, 83 | by?: { 84 | login?: string, 85 | type?: string, 86 | id?: number, 87 | } 88 | } 89 | 90 | export default class CodeScanningAlert { 91 | 92 | private readonly data: CodeScanningData; 93 | 94 | constructor(data: CodeScanningData) { 95 | this.data = data; 96 | } 97 | 98 | get id(): number { 99 | return this.data.number; 100 | } 101 | 102 | get url(): string { 103 | return this.data.html_url; 104 | } 105 | 106 | get created(): string { 107 | return this.data.created_at; 108 | } 109 | 110 | get dismissed(): AlertDismissal | null { 111 | if (!this.data.dismissed_at) { 112 | return null; 113 | } 114 | 115 | const result: AlertDismissal = { 116 | at: this.data.dismissed_at, 117 | reason: this.data.dismissed_reason, 118 | }; 119 | 120 | if (this.data.dismissed_by) { 121 | result.by = { 122 | login: this.data.dismissed_by.login, 123 | type: this.data.dismissed_by.type, 124 | id: this.data.dismissed_by.id, 125 | //TODO these were invalid 126 | // avatar: this._data.dismissed_at.avatar_url, 127 | // url: this._data.dismissed_at.html_url, 128 | }; 129 | } 130 | 131 | return result; 132 | } 133 | 134 | get severity(): string { 135 | // return this.rule ? this.rule.severity : null; 136 | return this.rule.severity; 137 | } 138 | 139 | get state(): string { 140 | return this.data.state; 141 | } 142 | 143 | get rule(): Rule { 144 | return this.data.rule; 145 | } 146 | 147 | get ruleId(): string { 148 | return this.rule.id; 149 | } 150 | 151 | get ruleDescription(): string { 152 | return this.rule.description; 153 | } 154 | 155 | get toolName(): string | null { 156 | return this.data.tool ? this.data.tool.name : null; 157 | } 158 | 159 | get toolVersion(): string | null { 160 | return this.data.tool ? this.data.tool.version : null; 161 | } 162 | } -------------------------------------------------------------------------------- /src/codeScanning/CodeScanningResults.ts: -------------------------------------------------------------------------------- 1 | import CodeScanningAlert from './CodeScanningAlert'; 2 | 3 | export default class CodeScanningResults { 4 | 5 | private data: CodeScanningAlert[]; 6 | 7 | constructor() { 8 | this.data = []; 9 | } 10 | 11 | addCodeScanningAlert(alert: CodeScanningAlert) { 12 | this.data.push(alert); 13 | } 14 | 15 | getTools(): string[] { 16 | const result: string[] = []; 17 | 18 | this.data.forEach(alert => { 19 | const toolName = alert.toolName; 20 | 21 | if (toolName && result.indexOf(toolName) === -1) { 22 | result.push(toolName); 23 | } 24 | }) 25 | 26 | return result; 27 | } 28 | 29 | getCodeQLScanningAlerts(): CodeScanningAlert[] { 30 | return this.data.filter(value => { 31 | //TODO this is now reporting CodeQL command-line toolchain as the name of the tool! 32 | // Need to follow up on this with GHAS team on what to expect in the future. 33 | return `${value.toolName}`.toLowerCase().startsWith('codeql'); 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /src/codeScanning/GitHubCodeScanning.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Octokit } from '@octokit/rest'; 3 | import GitHubCodeScanning from './GitHubCodeScanning'; 4 | import { getGitHubToken } from '../testUtils'; 5 | 6 | describe('GitHubDependencies', () => { 7 | 8 | const testRepo = { 9 | owner: 'octodemo', 10 | repo: 'demo-vulnerabilities-ghas' 11 | }; 12 | 13 | const ghasReportingRepo = { 14 | owner: 'octodemo', 15 | repo: 'ghas-reporting' 16 | }; 17 | 18 | const pmAdvanceSecurityJava = { 19 | owner: 'peter-murray', 20 | repo: 'advanced-security-java' 21 | }; 22 | 23 | let codeScanning: GitHubCodeScanning; 24 | 25 | before(() => { 26 | const octokit = new Octokit({auth: getGitHubToken()}); 27 | codeScanning = new GitHubCodeScanning(octokit); 28 | }); 29 | 30 | 31 | describe('getOpenCodeScanningAlerts()', () => { 32 | 33 | it(`from ${JSON.stringify(testRepo)}`, async () => { 34 | const results = await codeScanning.getOpenCodeScanningAlerts(testRepo) 35 | , tools = results.getTools() 36 | ; 37 | 38 | expect(tools).to.have.length(1); 39 | expect(tools[0]).to.equal('CodeQL'); 40 | }); 41 | 42 | it(`from ${JSON.stringify(ghasReportingRepo)}`, async () => { 43 | const results = await codeScanning.getOpenCodeScanningAlerts(ghasReportingRepo) 44 | , tools = results.getTools() 45 | ; 46 | 47 | expect(tools).to.have.length(1); 48 | expect(tools[0]).to.equal('-CodeQL-'); 49 | }); 50 | 51 | it (`from ${JSON.stringify(pmAdvanceSecurityJava)}`, async () => { 52 | const results = await codeScanning.getOpenCodeScanningAlerts(pmAdvanceSecurityJava); 53 | 54 | expect(results.getCodeQLScanningAlerts()).to.have.length(26);//TODO flaky test, sort this out 55 | }); 56 | }); 57 | 58 | }); -------------------------------------------------------------------------------- /src/codeScanning/GitHubCodeScanning.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/rest'; 2 | import { CodeScanningListAlertsForRepoResponseData, Endpoints } from '@octokit/types'; 3 | 4 | import CodeScanningAlert, { CodeScanningData } from './CodeScanningAlert'; 5 | import CodeScanningResults from './CodeScanningResults'; 6 | 7 | type listCodeScanningAlertsParameters = Endpoints['GET /repos/:owner/:repo/code-scanning/alerts']['parameters']; 8 | 9 | type Repo = { 10 | owner: string, 11 | repo: string 12 | } 13 | 14 | export default class GitHubCodeScanning { 15 | 16 | private readonly octokit: Octokit; 17 | 18 | constructor(octokit) { 19 | this.octokit = octokit; 20 | } 21 | 22 | getOpenCodeScanningAlerts(repo: Repo): Promise { 23 | return getCodeScanning(this.octokit, repo, 'open'); 24 | } 25 | 26 | getClosedCodeScanningAlerts(repo: Repo): Promise { 27 | return getCodeScanning(this.octokit, repo, 'dismissed'); 28 | } 29 | } 30 | 31 | function getCodeScanning(octokit: Octokit, 32 | repo: Repo, 33 | state: 'open' | 'fixed' | 'dismissed'): Promise { 34 | 35 | const params: listCodeScanningAlertsParameters = { 36 | owner: repo.owner, 37 | repo: repo.repo, 38 | state: state 39 | }; 40 | 41 | return octokit.paginate('GET /repos/:owner/:repo/code-scanning/alerts', params) 42 | //@ts-ignore 43 | .then((alerts: CodeScanningListAlertsForRepoResponseData) => { 44 | const results: CodeScanningResults = new CodeScanningResults(); 45 | 46 | alerts.forEach((alert: CodeScanningData) => { 47 | results.addCodeScanningAlert(new CodeScanningAlert(alert)); 48 | }); 49 | 50 | return results; 51 | }); 52 | } -------------------------------------------------------------------------------- /src/dependencies/Dependency.ts: -------------------------------------------------------------------------------- 1 | import { DependencySetDependencyData } from './DependencyTypes'; 2 | 3 | export default class Dependency { 4 | 5 | private readonly data: DependencySetDependencyData; 6 | 7 | constructor(data: DependencySetDependencyData) { 8 | this.data = data; 9 | } 10 | 11 | get name(): string { 12 | return this.data.node.packageName; 13 | } 14 | 15 | get packageType(): string { 16 | return this.data.node.packageManager; 17 | } 18 | 19 | get version(): string { 20 | return this.data.node.requirements; 21 | } 22 | } -------------------------------------------------------------------------------- /src/dependencies/DependencySet.ts: -------------------------------------------------------------------------------- 1 | import Dependency from './Dependency'; 2 | 3 | import { DependencySetData } from './DependencyTypes'; 4 | 5 | export default class DependencySet { 6 | 7 | private readonly data: DependencySetData; 8 | 9 | constructor(data: DependencySetData) { 10 | this.data = data; 11 | } 12 | 13 | get filename(): string { 14 | return this.data.node.filename; 15 | } 16 | 17 | get count(): number { 18 | return this.data.node.dependenciesCount || 0; 19 | } 20 | 21 | get path(): string { 22 | return this.data.node.blobPath; 23 | } 24 | 25 | get isValid(): boolean { 26 | return this.parsable && !this.exceededMaxSize; 27 | } 28 | 29 | get parsable(): boolean { 30 | return this.data.node.parseable; 31 | } 32 | 33 | get exceededMaxSize(): boolean { 34 | return this.data.node.exceedsMaxSize; 35 | } 36 | 37 | get dependencies(): Dependency[] { 38 | const deps = this.data.node.dependencies.edges; 39 | 40 | if (deps) { 41 | return deps.map(dep => { 42 | return new Dependency(dep); 43 | }); 44 | } 45 | return []; 46 | } 47 | } -------------------------------------------------------------------------------- /src/dependencies/DependencyTypes.ts: -------------------------------------------------------------------------------- 1 | export const QUERY_SECURITY_VULNERABILITIES = ` 2 | query users($organizationName: String!, $repositoryName: String!, $cursor: String) { 3 | 4 | repository(owner: $organizationName, name: $repositoryName) { 5 | vulnerabilityAlerts(first: 100, after: $cursor) { 6 | totalCount 7 | pageInfo { 8 | hasNextPage 9 | endCursor 10 | } 11 | nodes { 12 | id 13 | createdAt 14 | dismisser { 15 | login 16 | name 17 | } 18 | dismissedAt 19 | dismissReason 20 | vulnerableManifestFilename 21 | vulnerableRequirements 22 | vulnerableManifestPath 23 | securityVulnerability{ 24 | package { 25 | ecosystem 26 | name 27 | } 28 | severity 29 | vulnerableVersionRange 30 | } 31 | securityAdvisory{ 32 | databaseId 33 | id 34 | summary 35 | severity 36 | description 37 | ghsaId 38 | identifiers { 39 | type 40 | value 41 | } 42 | permalink 43 | publishedAt 44 | } 45 | } 46 | } 47 | } 48 | } 49 | `; 50 | 51 | export type RepositoryVulnerabilityAlerts = { 52 | repository: { 53 | vulnerabilityAlerts: { 54 | totalCount: number, 55 | pageInfo: { 56 | hasNextPage: boolean, 57 | endCursor: string, 58 | }, 59 | nodes: VulnerabilityAlert[] 60 | } 61 | } 62 | } 63 | 64 | export type VulnerabilityAlert = { 65 | id: string, 66 | createdAt: string, 67 | dismisser: { 68 | login: string, 69 | name: string, 70 | }, 71 | dismissedAt: string, 72 | dismissReason: string, 73 | vulnerableManifestFilename: string, 74 | vulnerableRequirements: string, 75 | vulnerableManifestPath 76 | securityVulnerability: SecurityVulnerability 77 | securityAdvisory: SecurityAdvisory 78 | } 79 | 80 | export type SecurityVulnerability = { 81 | package: { 82 | ecosystem: string, 83 | name: string 84 | } 85 | severity: string, 86 | vulnerableVersionRange: string 87 | } 88 | 89 | export type SecurityAdvisory = { 90 | databaseId: string, 91 | id: string, 92 | summary: string 93 | severity: string, 94 | description: string 95 | ghsaId: string, 96 | identifiers: { 97 | type: string, 98 | value: string, 99 | } 100 | permalink: string 101 | publishedAt: string 102 | } 103 | 104 | export const QUERY_DEPENDENCY_GRAPH = ` 105 | query ($organizationName: String!, $repositoryName: String!, $cursor: String){ 106 | repository(owner: $organizationName name: $repositoryName) { 107 | name 108 | dependencyGraphManifests(first: 100, after: $cursor) { 109 | pageInfo { 110 | hasNextPage 111 | endCursor 112 | } 113 | totalCount 114 | edges { 115 | node { 116 | filename 117 | dependenciesCount 118 | blobPath 119 | exceedsMaxSize 120 | parseable 121 | dependencies{ 122 | edges { 123 | node { 124 | packageName 125 | packageManager 126 | requirements 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | `; 136 | 137 | export type DependencyGraphResult = { 138 | repository: { 139 | name: string, 140 | dependencyGraphManifests: { 141 | pageInfo: { 142 | hasNextPage: boolean, 143 | endCursor: string, 144 | }, 145 | totalCount: number, 146 | edges: DependencySetData[] 147 | } 148 | } 149 | } 150 | 151 | export type DependencySetData = { 152 | node: { 153 | filename: string 154 | dependenciesCount: number 155 | blobPath: string 156 | exceedsMaxSize: boolean 157 | parseable: boolean 158 | dependencies: { 159 | edges: DependencySetDependencyData [] 160 | } 161 | } 162 | } 163 | 164 | export type DependencySetDependencyData = { 165 | node: { 166 | packageName: string 167 | packageManager: string 168 | requirements: string 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/dependencies/GitHubDependencies.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import GitHubDependencies from './GitHubDependencies'; 3 | 4 | import { Octokit } from '@octokit/rest'; 5 | import DependencySet from './DependencySet'; 6 | import Dependency from './Dependency'; 7 | import { getGitHubToken } from '../testUtils'; 8 | 9 | describe('GitHubDependencies', function () { 10 | 11 | this.timeout(10 * 1000); 12 | 13 | const testRepo = { 14 | owner: 'octodemo', 15 | repo: 'demo-vulnerabilities-ghas' 16 | }; 17 | 18 | let ghDeps: GitHubDependencies; 19 | 20 | before(() => { 21 | const octokit = new Octokit({auth: getGitHubToken()}); 22 | ghDeps = new GitHubDependencies(octokit); 23 | }); 24 | 25 | describe('#getAllDependencies()', () => { 26 | 27 | it(`from ${JSON.stringify(testRepo)}`, async () => { 28 | const results: DependencySet[] = await ghDeps.getAllDependencies(testRepo); 29 | 30 | expect(results).to.have.length.greaterThan(0); 31 | expect(results[0]).to.have.property('count').to.be.greaterThan(0); 32 | 33 | const dep: Dependency = results[0].dependencies[0]; 34 | expect(dep.packageType).to.equal('MAVEN'); 35 | }); 36 | }); 37 | 38 | describe('#getAllVulnerabilities()', () => { 39 | 40 | it(`from ${JSON.stringify(testRepo)}`, async () => { 41 | const results = await ghDeps.getAllVulnerabilities(testRepo); 42 | 43 | expect(results).to.have.length.greaterThan(10); 44 | }); 45 | }); 46 | }); -------------------------------------------------------------------------------- /src/dependencies/GitHubDependencies.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/rest'; 2 | import { RequestHeaders, RequestParameters } from '@octokit/types'; 3 | 4 | import { 5 | QUERY_SECURITY_VULNERABILITIES, 6 | QUERY_DEPENDENCY_GRAPH, 7 | VulnerabilityAlert, 8 | DependencySetData, RepositoryVulnerabilityAlerts, DependencyGraphResult 9 | } from './DependencyTypes'; 10 | 11 | import Vulnerability from './Vulnerability'; 12 | import DependencySet from './DependencySet'; 13 | 14 | type Repo = { 15 | owner: string, 16 | repo: string, 17 | } 18 | 19 | 20 | export default class GitHubDependencies { 21 | 22 | private readonly octokit: Octokit; 23 | 24 | constructor(octokit) { 25 | this.octokit = octokit; 26 | } 27 | 28 | async getAllVulnerabilities(repo: Repo): Promise { 29 | function extractVulnerabilityAlerts(data: RepositoryVulnerabilityAlerts): VulnerabilityAlert[] { 30 | return data.repository.vulnerabilityAlerts.nodes; 31 | } 32 | 33 | const data: VulnerabilityAlert[] = await this.getPaginatedQuery( 34 | QUERY_SECURITY_VULNERABILITIES, 35 | {organizationName: repo.owner, repositoryName: repo.repo}, 36 | 'repository.vulnerabilityAlerts.pageInfo', 37 | extractVulnerabilityAlerts 38 | ); 39 | 40 | return data.map(val => { 41 | return new Vulnerability(val); 42 | }); 43 | } 44 | 45 | async getAllDependencies(repo: Repo): Promise { 46 | function extractDependencySetData(data: DependencyGraphResult): DependencySetData[] { 47 | return data.repository.dependencyGraphManifests.edges; 48 | } 49 | 50 | const data = await this.getPaginatedQuery( 51 | QUERY_DEPENDENCY_GRAPH, 52 | {organizationName: repo.owner, repositoryName: repo.repo}, 53 | 'repository.dependencyGraphManifests.pageInfo', 54 | extractDependencySetData, 55 | {accept: 'application/vnd.github.hawkgirl-preview+json'} 56 | ); 57 | 58 | return data.map(node => { 59 | return new DependencySet(node); 60 | }); 61 | } 62 | 63 | async getPaginatedQuery(query: string, 64 | parameters: Object, 65 | pageInfoPath: string, 66 | extractResultsFn: (val: T) => Y[], 67 | headers?): Promise { 68 | const octokit = this.octokit 69 | , results: Y[] = [] 70 | , queryParameters = Object.assign({cursor: null}, parameters) 71 | ; 72 | 73 | let hasNextPage = false; 74 | do { 75 | const graphqlParameters = buildGraphQLParameters(query, parameters, headers) 76 | , queryResult = await octokit.graphql(graphqlParameters) 77 | ; 78 | 79 | // @ts-ignore 80 | const extracted: Y = extractResultsFn(queryResult); 81 | // @ts-ignore 82 | results.push(...extracted); 83 | 84 | const pageInfo = getObject(queryResult, ...pageInfoPath.split('.')); 85 | hasNextPage = pageInfo ? pageInfo.hasNextPage : false; 86 | if (hasNextPage) { 87 | queryParameters.cursor = pageInfo.endCursor; 88 | } 89 | } while (hasNextPage); 90 | 91 | return results; 92 | } 93 | } 94 | 95 | function buildGraphQLParameters(query: string, parameters?: Object, headers?: RequestHeaders): RequestParameters { 96 | const result: RequestParameters = { 97 | ...(parameters || {}), 98 | query: query, 99 | }; 100 | 101 | if (headers) { 102 | result.headers = headers; 103 | } 104 | 105 | return result; 106 | } 107 | 108 | function getObject(target, ...path) { 109 | if (target !== null && target !== undefined) { 110 | const value = target[path[0]]; 111 | 112 | if (path.length > 1) { 113 | return getObject(value, ...path.slice(1)); 114 | } else { 115 | return value; 116 | } 117 | } 118 | return null; 119 | } -------------------------------------------------------------------------------- /src/dependencies/Vulnerability.ts: -------------------------------------------------------------------------------- 1 | import { SecurityAdvisory, SecurityVulnerability, VulnerabilityAlert } from './DependencyTypes'; 2 | 3 | export type DismissedBy = { 4 | user: { 5 | login: string, 6 | name: string, 7 | }, 8 | reason: string, 9 | at: string 10 | }; 11 | 12 | export type VulnerableSource = { 13 | manifest: string, 14 | version: string, 15 | path: string, 16 | } 17 | 18 | export default class Vulnerability { 19 | 20 | private readonly data: VulnerabilityAlert; 21 | 22 | constructor(data: VulnerabilityAlert) { 23 | this.data = data; 24 | } 25 | 26 | get created(): string { 27 | return this.data.createdAt; 28 | } 29 | 30 | get isDismissed(): boolean { 31 | return !!this.data.dismisser; 32 | } 33 | 34 | get dismissedBy(): DismissedBy { 35 | return { 36 | user: { 37 | login: this.data.dismisser.login, 38 | name: this.data.dismisser.name 39 | }, 40 | reason: this.data.dismissReason, 41 | at: this.data.dismissedAt, 42 | }; 43 | } 44 | 45 | get severity(): string { 46 | return this.data.securityVulnerability.severity; 47 | } 48 | 49 | get vulnerability(): SecurityVulnerability { 50 | return Object.assign({}, this.data.securityVulnerability); 51 | } 52 | 53 | get advisory(): SecurityAdvisory { 54 | return Object.assign({}, this.data.securityAdvisory); 55 | } 56 | 57 | get source(): VulnerableSource { 58 | return { 59 | manifest: this.data.vulnerableManifestFilename, 60 | version: this.data.vulnerableRequirements, 61 | path: this.data.vulnerableManifestPath 62 | }; 63 | } 64 | 65 | get publishedAt(): string { 66 | return this.advisory.publishedAt; 67 | } 68 | 69 | get link(): string { 70 | return this.advisory.permalink; 71 | } 72 | } -------------------------------------------------------------------------------- /src/executable.ts: -------------------------------------------------------------------------------- 1 | import ReportGenerator, { ReportGeneratorConfig } from './ReportGenerator'; 2 | import { Octokit } from '@octokit/rest'; 3 | 4 | import path from 'path'; 5 | 6 | const {program} = require('commander'); 7 | program.name('github-security-report'); 8 | program.version(require('../package.json').version); 9 | 10 | program.requiredOption('-t, --token ', 'github access token'); 11 | program.requiredOption('-r --repository ', 'github repository, owner/repo_name format'); 12 | program.option('-s --sarif-directory ', 'the SARIF report directory to load reports from', '../results'); 13 | program.option('-o --output-directory ', 'output directory for summary report', '.'); 14 | program.option('--github-api-url ', 'GitHub API URL', 'https://api.github.com') 15 | 16 | program.parse(process.argv); 17 | const opts = program.opts(); 18 | 19 | const reportGenerateConfig: ReportGeneratorConfig = { 20 | repository: opts.repository, 21 | octokit: new Octokit({auth: opts.token, baseUrl: opts.url}), 22 | sarifReportDirectory: getPath(opts.sarifDirectory), 23 | outputDirectory: getPath(opts.outputDirectory), 24 | templating: { 25 | name: 'summary' 26 | } 27 | } 28 | 29 | async function execute(reportGenerateConfig: ReportGeneratorConfig) { 30 | try { 31 | const generator = new ReportGenerator(reportGenerateConfig); 32 | console.log(`Generating Security report for ${reportGenerateConfig.repository}...`); 33 | const file = await generator.run(); 34 | console.log(`Summary Report generated: ${file}`); 35 | 36 | } catch (err) { 37 | console.log(err.stack); 38 | console.error(err.message); 39 | console.error(); 40 | program.help({error: true}); 41 | } 42 | } 43 | 44 | execute(reportGenerateConfig); 45 | 46 | 47 | function getPath(value) { 48 | if (path.isAbsolute(value)) { 49 | return value; 50 | } else { 51 | return path.normalize(path.join(process.cwd(), value)); 52 | } 53 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import ReportGenerator from './ReportGenerator'; 2 | 3 | import * as core from '@actions/core'; 4 | import { Octokit } from '@octokit/rest'; 5 | 6 | async function run(): Promise { 7 | try { 8 | const token = getRequiredInputValue('token'); 9 | 10 | const generator = new ReportGenerator({ 11 | repository: getRequiredInputValue('repository'), 12 | octokit: new Octokit({auth: token}), 13 | 14 | sarifReportDirectory: getRequiredInputValue('sarifReportDir'), 15 | outputDirectory: getRequiredInputValue('outputDir'), 16 | 17 | templating: { 18 | name: 'summary' 19 | } 20 | }); 21 | 22 | const file = await generator.run(); 23 | console.log(file); 24 | } catch (err) { 25 | core.setFailed(err.message); 26 | } 27 | } 28 | 29 | run(); 30 | 31 | function getRequiredInputValue(key: string): string { 32 | return core.getInput(key, {required: true}); 33 | } 34 | -------------------------------------------------------------------------------- /src/pdf/pdfWriter.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { mkdirP } from '@actions/io'; 3 | import { createPDF } from './pdfWriter'; 4 | import { getTestDirectoryFilePath } from '../testUtils'; 5 | 6 | describe('pdfWriter', function () { 7 | 8 | this.timeout(30 * 1000); 9 | 10 | it('should generate a simple pdf', async () => { 11 | const html = '

Hello World

' 12 | , file = getTestDirectoryFilePath('test.pdf') 13 | ; 14 | 15 | // Ensure the directory exists 16 | await mkdirP(getTestDirectoryFilePath()); 17 | 18 | const generatePdf = await createPDF(html, file) 19 | expect(generatePdf).to.equal(file); 20 | //TODO check size 21 | }); 22 | }); -------------------------------------------------------------------------------- /src/pdf/pdfWriter.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | 3 | const puppeteer = require('puppeteer-core'); 4 | 5 | export function createPDF(html: string, file: string): Promise { 6 | 7 | const fetcher = puppeteer.createBrowserFetcher({path: os.tmpdir()}); 8 | 9 | return fetcher.download('782078')//TODO need to store and inject this 10 | .then(revisionInfo => { 11 | return puppeteer.launch({executablePath: revisionInfo.executablePath}) 12 | .then(browser => { 13 | return browser.newPage() 14 | .then(page => { 15 | return page.setContent(html) 16 | .then(() => { 17 | return page.pdf({path: file, format: 'A4'}) 18 | }); 19 | }) 20 | .then(() => { 21 | return browser.close(); 22 | }); 23 | }) 24 | .then(() => { 25 | return file; 26 | }); 27 | }); 28 | }; -------------------------------------------------------------------------------- /src/sarif/CodeScanningResult.ts: -------------------------------------------------------------------------------- 1 | interface Location { 2 | artifactLocation: { 3 | uri: string 4 | } 5 | region: string 6 | } 7 | 8 | export class PhysicalLocation { 9 | 10 | private _location: Location 11 | 12 | constructor(location: Location) { 13 | this._location = location; 14 | } 15 | 16 | get artifactLocation(): string { 17 | return this._location.artifactLocation.uri; 18 | } 19 | 20 | get region(): string { 21 | return this._location.region; 22 | } 23 | } 24 | 25 | interface Sarif { 26 | ruleId: string, 27 | ruleIndex: number, 28 | message: { 29 | text: string, 30 | }, 31 | locations: Location[], 32 | } 33 | 34 | export default class CodeScanningResult { 35 | 36 | readonly locations: PhysicalLocation[] | null; 37 | 38 | private _sarif: Sarif 39 | 40 | constructor(sarifResult) { 41 | this._sarif = sarifResult; 42 | this.locations = extractLocations(sarifResult); 43 | } 44 | 45 | get ruleId() : string { 46 | return this._sarif.ruleId; 47 | } 48 | 49 | get ruleIndex(): number { 50 | return this._sarif.ruleIndex; 51 | } 52 | 53 | get message(): string { 54 | return this._sarif.message.text; 55 | } 56 | } 57 | 58 | function extractLocations(sarif: Sarif): PhysicalLocation[] | null { 59 | if (sarif && sarif.locations) { 60 | const results: PhysicalLocation[] = []; 61 | 62 | sarif.locations.forEach(location => { 63 | results.push(new PhysicalLocation(location)); 64 | }); 65 | 66 | return results; 67 | } 68 | return null; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/sarif/CodeScanningRule.ts: -------------------------------------------------------------------------------- 1 | import {SarifRule} from './SarifDataTypes'; 2 | 3 | const CWE_REGEX = /external\/cwe\/(cwe-.*)/; 4 | 5 | export default class CodeScanningRule { 6 | 7 | private readonly sarifRule: SarifRule; 8 | 9 | readonly cwes: string[]; 10 | 11 | constructor(sarifRule: SarifRule) { 12 | this.sarifRule = sarifRule; 13 | this.cwes = getCWEs(sarifRule.properties.tags); 14 | } 15 | 16 | get id(): string { 17 | return this.sarifRule.id; 18 | } 19 | 20 | get name() : string{ 21 | return this.sarifRule.name; 22 | } 23 | 24 | get shortDescription(): string { 25 | return this.sarifRule.shortDescription.text; 26 | } 27 | 28 | get description(): string { 29 | return this.sarifRule.fullDescription.text; 30 | } 31 | 32 | get tags(): Array { 33 | return this.sarifRule.properties.tags; 34 | } 35 | 36 | get severity(): string { 37 | return this.sarifRule.properties['problem.severity']; 38 | } 39 | 40 | get precision() : string{ 41 | return this.sarifRule.properties.precision; 42 | } 43 | 44 | get kind() : string{ 45 | return this.sarifRule.properties.kind; 46 | } 47 | 48 | get defaultConfigurationLevel(): string { 49 | return this.sarifRule.defaultConfiguration.level; 50 | } 51 | } 52 | 53 | function getCWEs(tags: string[]): string[] { 54 | const cwes: string[] = []; 55 | 56 | if (tags) { 57 | tags.forEach(tag => { 58 | const match = CWE_REGEX.exec(tag); 59 | 60 | if (match) { 61 | // @ts-ignore 62 | cwes.push(match[1]); 63 | } 64 | }); 65 | } 66 | 67 | return cwes.sort(); 68 | } -------------------------------------------------------------------------------- /src/sarif/SarifDataTypes.ts: -------------------------------------------------------------------------------- 1 | export type SarifReportData = { 2 | version: string, 3 | runs: SarifRun[], 4 | } 5 | 6 | export type SarifRun = { 7 | tool: { 8 | driver: { 9 | name: string, 10 | rules: SarifRule[] 11 | } 12 | } 13 | } 14 | 15 | export type SarifRule = { 16 | id: string, 17 | name: string, 18 | shortDescription: { 19 | text: string, 20 | }, 21 | fullDescription: { 22 | text: string, 23 | }, 24 | properties: { 25 | tags: string[], 26 | precision: string, 27 | kind: string, 28 | 29 | }, 30 | defaultConfiguration: { 31 | level: string, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/sarif/SarifReport.ts: -------------------------------------------------------------------------------- 1 | import CodeScanningRule from "./CodeScanningRule"; 2 | import {SarifReportData, SarifRule} from './SarifDataTypes'; 3 | 4 | export default class SarifReport { 5 | 6 | private readonly data: SarifReportData; 7 | 8 | readonly rules: CodeScanningRule[]; 9 | 10 | constructor(data: SarifReportData) { 11 | this.data = data; 12 | this.rules = getRules(data) || []; 13 | } 14 | 15 | get cweList(): string[] { 16 | const result = this.rules.reduce((cwes: string[], rule) => { 17 | return cwes.concat(rule.cwes) 18 | }, []); 19 | return unique(result).sort(); 20 | } 21 | } 22 | 23 | 24 | function getRules(report: SarifReportData) { 25 | let sarifRules: SarifRule[] | null = null; 26 | 27 | if (report.version === '2.1.0') { 28 | if (report.runs) { 29 | report.runs.forEach(run => { 30 | if (run.tool.driver.name === 'CodeQL') { //TODO could support other tools 31 | sarifRules = run.tool.driver.rules; 32 | } 33 | }); 34 | } 35 | } else { 36 | throw new Error(`Unsupported version: ${report.version}`) 37 | } 38 | 39 | return getAppliedRuleDetails(sarifRules); 40 | } 41 | 42 | 43 | function getAppliedRuleDetails(sarifRules: SarifRule[] | null): CodeScanningRule[] | null { 44 | if (sarifRules) { 45 | return sarifRules.map(rule => { 46 | return new CodeScanningRule(rule) 47 | }); 48 | } 49 | 50 | return null; 51 | } 52 | 53 | 54 | function unique(array: string[]): string[] { 55 | return array.filter((val, idx, self) => { 56 | return self.indexOf(val) === idx 57 | }); 58 | } -------------------------------------------------------------------------------- /src/sarif/SarifReportFinder.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as fs from 'fs'; 3 | import SarifReport from './SarifReport'; 4 | 5 | export type SarifFile = { 6 | file: string, 7 | payload: SarifReport 8 | } 9 | 10 | export default class SarifReportFinder { 11 | 12 | private readonly dir: string; 13 | 14 | constructor(dir: string) { 15 | this.dir = dir; 16 | } 17 | 18 | getSarifFiles(): Promise { 19 | const dir = this.dir 20 | , promises: Promise[] = [] 21 | ; 22 | 23 | if (!fs.existsSync(dir)) { 24 | throw new Error(`SARIF Finder, path "${dir}", does not exist.`); 25 | } 26 | 27 | console.log(`SARIF File Finder, processing: ${dir}`); 28 | if (fs.lstatSync(dir).isDirectory()) { 29 | console.log(` is a directory, looking for files`); 30 | 31 | const files = fs.readdirSync(dir) // TODO use promises here 32 | .filter(f => f.endsWith('.sarif')) 33 | .map(f => path.resolve(dir, f)); 34 | 35 | console.log(` SARIF files detected: ${JSON.stringify(files)}`); 36 | if (files) { 37 | files.forEach(f => { 38 | promises.push(loadFileContents(f)); 39 | }); 40 | } 41 | } 42 | 43 | if (promises.length > 0) { 44 | return Promise.all(promises); 45 | } else { 46 | return Promise.resolve([]); 47 | } 48 | } 49 | } 50 | 51 | function loadFileContents(file: string): Promise { 52 | return fs.promises.open(file, 'r') 53 | .then(fileHandle => { 54 | return fileHandle.readFile() 55 | .then(content => { 56 | fileHandle.close(); 57 | try { 58 | return JSON.parse(content.toString('utf8')); 59 | } catch (err) { 60 | throw new Error(`Failed to parse JSON from SARIF file '${file}': ${err}`); 61 | } 62 | }) 63 | .then(data => { 64 | return { 65 | file: file, 66 | payload: new SarifReport(data), 67 | }; 68 | }) 69 | }); 70 | } -------------------------------------------------------------------------------- /src/templating/ReportData.ts: -------------------------------------------------------------------------------- 1 | import Vulnerability from '../dependencies/Vulnerability'; 2 | import DependencySet from '../dependencies/DependencySet'; 3 | import { SarifFile } from '../sarif/SarifReportFinder'; 4 | import CodeScanningResults from '../codeScanning/CodeScanningResults'; 5 | import CodeScanningRule from '../sarif/CodeScanningRule'; 6 | import { 7 | AlertSummary, 8 | CodeScanningRules, CodeScanResults, CodeScanSummary, 9 | CollectedData, 10 | CWECoverage, Dependencies, 11 | DependencySummary, 12 | JsonPayload, Manifest, 13 | Repo, 14 | RuleData, ServerityToVulnerabilities, SeverityToAlertSummary 15 | } from './ReportTypes'; 16 | 17 | export default class ReportData { 18 | 19 | private readonly data: CollectedData; 20 | 21 | constructor(data: CollectedData) { 22 | this.data = data || {}; 23 | } 24 | 25 | get githubRepo(): Repo { 26 | return this.data.github || {}; 27 | } 28 | 29 | get vulnerabilities(): Vulnerability[] { 30 | return this.data.vulnerabilities || []; 31 | } 32 | 33 | get dependencies(): DependencySet[] { 34 | return this.data.dependencies || []; 35 | } 36 | 37 | get openDependencyVulnerabilities(): Vulnerability[] { 38 | return this.vulnerabilities.filter(vuln => { 39 | return !vuln.isDismissed; 40 | }); 41 | } 42 | 43 | get closedDependencyVulnerabilities(): Vulnerability[] { 44 | return this.vulnerabilities.filter(vuln => { 45 | return vuln.isDismissed; 46 | }); 47 | } 48 | 49 | get openCodeScanResults(): CodeScanningResults { 50 | return this.data.codeScanningOpen || {}; 51 | } 52 | 53 | get closedCodeScanResults(): CodeScanningResults { 54 | return this.data.codeScanningClosed || {}; 55 | } 56 | 57 | get sarifReports(): SarifFile[] { 58 | return this.data.sarifReports || []; 59 | } 60 | 61 | get codeScanningRules(): CodeScanningRules { 62 | const result = {}; 63 | 64 | this.sarifReports.forEach(report => { 65 | // Each report is an object of {file, payload} keys 66 | const rules = report.payload.rules; 67 | 68 | if (rules) { 69 | rules.forEach(rule => { 70 | result[rule.id] = rule; 71 | }); 72 | } 73 | }); 74 | 75 | return result; 76 | } 77 | 78 | getJSONPayload(): JsonPayload { 79 | const data = { 80 | github: this.githubRepo, 81 | metadata: { 82 | created: new Date().toISOString(), 83 | }, 84 | sca: { 85 | dependencies: this.getDependencySummary(), 86 | vulnerabilities: { 87 | total: this.openDependencyVulnerabilities.length, 88 | bySeverity: this.getVulnerabilitiesBySeverity() 89 | }, 90 | }, 91 | scanning: { 92 | rules: this.getAppliedCodeScanningRules(), 93 | cwe: this.getCWECoverage() || {}, 94 | results: this.getCodeScanSummary(), 95 | } 96 | }; 97 | return data; 98 | } 99 | 100 | getCWECoverage(): CWECoverage | null { 101 | const rules = this.getAppliedCodeScanningRules(); 102 | 103 | if (rules) { 104 | const result: {[key: string]: RuleData[]} = {}; 105 | 106 | rules.forEach(rule => { 107 | const cwes = rule.cwe; 108 | 109 | if (cwes) { 110 | cwes.forEach(cwe => { 111 | if (!result[cwe]) { 112 | result[cwe] = []; 113 | } 114 | 115 | result[cwe].push(rule); 116 | }); 117 | } 118 | }); 119 | 120 | return { 121 | cweToRules: result, 122 | cwes: Object.keys(result) 123 | }; 124 | } 125 | 126 | return null; 127 | } 128 | 129 | 130 | getDependencySummary(): DependencySummary { 131 | const unprocessed: Manifest[] = [] 132 | , processed: Manifest[] = [] 133 | , dependencies: Dependencies = {} 134 | ; 135 | 136 | let totalDeps = 0; 137 | 138 | this.dependencies.forEach(depSet => { 139 | totalDeps += depSet.count; 140 | 141 | const manifest = { 142 | filename: depSet.filename, 143 | path: depSet.path 144 | }; 145 | 146 | if (depSet.isValid) { 147 | processed.push(manifest); 148 | } else { 149 | unprocessed.push(manifest); 150 | } 151 | 152 | const identifiedDeps = depSet.dependencies; 153 | if (identifiedDeps) { 154 | identifiedDeps.forEach(dep => { 155 | const type = dep.packageType.toLowerCase(); 156 | 157 | if (!dependencies[type]) { 158 | dependencies[type] = []; 159 | } 160 | 161 | dependencies[type].push({ 162 | name: dep.name, 163 | type: dep.packageType, 164 | version: dep.version, 165 | }); 166 | }); 167 | } 168 | }); 169 | 170 | return { 171 | manifests: { 172 | processed: processed, 173 | unprocessed: unprocessed, 174 | }, 175 | totalDependencies: totalDeps, 176 | dependencies: dependencies 177 | }; 178 | } 179 | 180 | getVulnerabilitiesBySeverity(): ServerityToVulnerabilities { 181 | const result = {}; 182 | 183 | // Obtain third party artifacts ranked by severity 184 | const vulnerabilities = this.openDependencyVulnerabilities; 185 | vulnerabilities.forEach(vulnerability => { 186 | const severity = vulnerability.severity.toLowerCase(); 187 | 188 | if (!result[severity]) { 189 | result[severity] = []; 190 | } 191 | result[severity].push(vulnerability); 192 | }); 193 | 194 | return result; 195 | } 196 | 197 | getAppliedCodeScanningRules(): RuleData[] { 198 | const rules = this.codeScanningRules; 199 | 200 | if (rules) { 201 | return Object.values(rules).map(rule => { 202 | return getRuleData(rule); 203 | }); 204 | } 205 | 206 | return []; 207 | } 208 | 209 | getCodeScanSummary(): CodeScanSummary { 210 | const open = this.openCodeScanResults 211 | , closed = this.closedCodeScanResults 212 | , rules = this.codeScanningRules 213 | ; 214 | 215 | const data = { 216 | open: generateAlertSummary(open, rules), 217 | closed: generateAlertSummary(closed, rules), 218 | }; 219 | 220 | return data; 221 | } 222 | } 223 | 224 | function generateAlertSummary(open: CodeScanningResults, rules: CodeScanningRules): CodeScanResults { 225 | const result: SeverityToAlertSummary = {}; 226 | let total = 0; 227 | 228 | open.getCodeQLScanningAlerts().forEach(codeScanAlert => { 229 | const severity = codeScanAlert.severity 230 | , matchedRule = rules ? rules[codeScanAlert.ruleId] : null 231 | ; 232 | 233 | const summary: AlertSummary = { 234 | tool: codeScanAlert.toolName, 235 | name: codeScanAlert.ruleDescription, 236 | state: codeScanAlert.state, 237 | created: codeScanAlert.created, 238 | url: codeScanAlert.url, 239 | rule: { 240 | id: codeScanAlert.ruleId, 241 | } 242 | }; 243 | 244 | if (matchedRule) { 245 | summary.rule.details = matchedRule; 246 | } 247 | 248 | if (!result[severity]) { 249 | result[severity] = []; 250 | } 251 | result[severity].push(summary); 252 | total++; 253 | }); 254 | 255 | return { 256 | total: total, 257 | scans: result 258 | }; 259 | } 260 | 261 | function getRuleData(rule: CodeScanningRule): RuleData { 262 | return { 263 | name: rule.name, 264 | //TODO maybe id? 265 | severity: rule.severity, 266 | precision: rule.precision, 267 | kind: rule.kind, 268 | shortDescription: rule.shortDescription, 269 | description: rule.description, 270 | tags: rule.tags, 271 | cwe: rule.cwes, 272 | }; 273 | } 274 | 275 | //TODO this was not used 276 | // function getVulnerability(vuln) { 277 | // if (!vuln) { 278 | // return null; 279 | // } 280 | // 281 | // const data = { 282 | // created: vuln.created, 283 | // published: vuln.publishedAt, 284 | // severity: vuln.severity, 285 | // vulnerability: vuln.vulnerability, 286 | // advisory: vuln.advisory, 287 | // source: vuln.source, 288 | // link: vuln.link, 289 | // }; 290 | // 291 | // if (vuln.isDismissed()) { 292 | // data.dismissed = vuln.dismissedBy; 293 | // } 294 | // 295 | // return data; 296 | // } -------------------------------------------------------------------------------- /src/templating/ReportTypes.ts: -------------------------------------------------------------------------------- 1 | import CodeScanningRule from '../sarif/CodeScanningRule'; 2 | import Vulnerability from '../dependencies/Vulnerability'; 3 | import DependencySet from '../dependencies/DependencySet'; 4 | import { SarifFile } from '../sarif/SarifReportFinder'; 5 | import CodeScanningResults from '../codeScanning/CodeScanningResults'; 6 | 7 | export type RuleData = { 8 | name: string, 9 | severity: string, 10 | precision: string, 11 | kind: string, 12 | shortDescription: string, 13 | description: string, 14 | tags: string[], 15 | cwe: string[] 16 | } 17 | 18 | export type Repo = { 19 | owner: string, 20 | repo: string 21 | } 22 | 23 | export type CodeScanningRules = { 24 | [key: string]: CodeScanningRule 25 | } 26 | 27 | export type CollectedData = { 28 | github: Repo 29 | vulnerabilities: Vulnerability[], 30 | dependencies: DependencySet[], 31 | sarifReports: SarifFile[], 32 | codeScanningOpen: CodeScanningResults, 33 | codeScanningClosed: CodeScanningResults, 34 | } 35 | 36 | export type JsonPayload = { 37 | github: Repo, 38 | metadata: { 39 | created: string, 40 | } 41 | sca: { 42 | dependencies: DependencySummary 43 | vulnerabilities: { 44 | total: number 45 | bySeverity: ServerityToVulnerabilities 46 | } 47 | }, 48 | scanning: { 49 | rules: RuleData[], 50 | cwe: CWECoverage | {}, 51 | results: CodeScanSummary 52 | } 53 | } 54 | 55 | export type DependencySummary = { 56 | manifests: { 57 | processed: Manifest[], 58 | unprocessed: Manifest[], 59 | }, 60 | totalDependencies: number, 61 | dependencies: Dependencies 62 | } 63 | 64 | export type Manifest = { 65 | filename: string, 66 | path: string, 67 | } 68 | 69 | export type Dependencies = { 70 | [key: string]: Dependency[] 71 | } 72 | 73 | export type Dependency = { 74 | name: string, 75 | type: string, 76 | version: string 77 | } 78 | 79 | export type ServerityToVulnerabilities = { 80 | [key: string]: Vulnerability[] 81 | } 82 | 83 | export type AlertSummary = { 84 | tool: string | null, 85 | name: string, 86 | state: string, 87 | created: string, 88 | url: string, 89 | rule: { 90 | id: string 91 | details?: CodeScanningRule 92 | } 93 | } 94 | 95 | export type SeverityToAlertSummary = { 96 | [key: string]: AlertSummary[] 97 | } 98 | 99 | export type CodeScanResults = { 100 | total: number, 101 | scans: SeverityToAlertSummary 102 | } 103 | 104 | export type CWECoverage = { 105 | cweToRules: {[key: string]: RuleData[]}, 106 | cwes: string[] 107 | } 108 | 109 | export type CodeScanSummary = { 110 | open: CodeScanResults, 111 | closed: CodeScanResults 112 | } -------------------------------------------------------------------------------- /src/templating/Template.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import { expect } from 'chai'; 3 | import Template from './Template'; 4 | import { getSampleReportJsonDirectory, getTestDirectoryFilePath } from '../testUtils'; 5 | 6 | 7 | const OCTODEMO_GHAS_REPORTING = { 8 | directory: 'octodemo/ghas-reporting', 9 | json: 'payload.json', 10 | expectedSummary: 'summary.html' 11 | }; 12 | 13 | describe('Template', () => { 14 | 15 | [OCTODEMO_GHAS_REPORTING].forEach(config => { 16 | 17 | it(`should render ${config.directory}`, () => { 18 | const reporting = new Template() 19 | , data = readSampleFileAsJson(config.directory, 'payload.json') 20 | , fileContent = reporting.render(data, 'summary') 21 | ; 22 | 23 | fs.writeFileSync(getTestDirectoryFilePath(config.directory, 'summary.html'), fileContent); 24 | 25 | const expectedContent = getExpectedContents(config); 26 | expect(fileContent).to.equal(expectedContent); 27 | }); 28 | }); 29 | 30 | }); 31 | 32 | 33 | function getExpectedContents(config) { 34 | const content = fs.readFileSync(getSampleReportJsonDirectory(config.directory, config.expectedSummary)); 35 | return content.toString('utf-8'); 36 | } 37 | 38 | 39 | function readSampleFileAsJson(subDir, file) { 40 | const content = fs.readFileSync(getSampleReportJsonDirectory(...[subDir, file])); 41 | return JSON.parse(content.toString('utf-8')); 42 | } 43 | -------------------------------------------------------------------------------- /src/templating/Template.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | const path = require('path') 4 | , nunjucks = require('nunjucks') 5 | ; 6 | 7 | // Default templates as part of the action 8 | const EMBEDDED_TEMPLATES = path.join(__dirname, '..', '..', 'templates'); 9 | 10 | export default class Template { 11 | 12 | private readonly renderer; 13 | 14 | private readonly templatesDir: string; 15 | 16 | constructor(templatesDir?: string) { 17 | if (!templatesDir) { 18 | this.templatesDir = EMBEDDED_TEMPLATES; 19 | } else { 20 | this.templatesDir = templatesDir; 21 | } 22 | 23 | this.renderer = nunjucks.configure(this.templatesDir, {autoescape: true}) 24 | } 25 | 26 | render(data, template): string { 27 | const resolvedTemplateFilename = this.getValidatedTemplateFileName(template); 28 | const content = this.renderer.render(resolvedTemplateFilename, data); 29 | //TODO consider providing intermediate output 30 | return content; 31 | } 32 | 33 | getValidatedTemplateFileName(name): string { 34 | if (fs.existsSync(path.join(this.templatesDir, name))) { 35 | return name; 36 | } else { 37 | // Try our known supported extensions 38 | const found = ['html', 'j2'].filter(extension => { 39 | return fs.existsSync(path.join(this.templatesDir, `${name}.${extension}`)); 40 | }); 41 | 42 | if (found.length > 0) { 43 | return `${name}.${found[0]}`; 44 | } 45 | } 46 | 47 | throw new Error(`Failed to resolve a template file from directory ${this.templatesDir} with name "${name}"`); 48 | } 49 | } -------------------------------------------------------------------------------- /src/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | export function getTestDirectoryFilePath(...filePath): string { 4 | const args = [__dirname, '..', '_tmp', ...filePath]; 5 | return path.join(...args); 6 | } 7 | 8 | export function getSampleDataDirectory(...dir): string { 9 | const args = [__dirname, '..', 'samples', ...dir]; 10 | return path.join(...args); 11 | } 12 | 13 | export function getSampleSarifDirectory(...dir): string { 14 | const args = [__dirname, '..', 'samples', 'sarif', ...dir]; 15 | return path.join(...args); 16 | } 17 | 18 | export function getSampleReportJsonDirectory(...dir): string { 19 | const args = [__dirname, '..', 'samples', 'reportJson', ...dir]; 20 | return path.join(...args); 21 | } 22 | 23 | export function getGitHubToken(): string { 24 | const token = process.env['GH_TOKEN']; 25 | 26 | if (!token) { 27 | throw new Error('GitHub Token was not set for environment variable "GH_TOKEN"'); 28 | } 29 | return token; 30 | } -------------------------------------------------------------------------------- /summary_report_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peter-murray/github-security-report-action/42aa5945bd5b688f14868891df0ecae0024b3c60/summary_report_example.png -------------------------------------------------------------------------------- /templates/executive_summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security - Executive Summary 6 | 7 | 126 | 127 | 128 | 129 | 130 |
131 |
132 | 133 |
134 |
GitHub Advanced Security - Executive Summary
135 |
Prepared Sep 10, 2020
136 |
137 |
138 | 139 |
140 |

Executive Summary

141 |
142 |
143 | 144 |
145 |
Repository: octodemo/test-repo
146 |
147 | 148 |
149 | 150 |
151 | 152 |
153 |
Code Scanning Status
154 |
155 |
156 | Open: 0 157 |
158 |
159 |
160 | 161 |
162 | 163 |
164 | 165 |
166 |
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /templates/summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security Report 6 | 7 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 |
45 |
46 |
47 | 50 |

GitHub Advanced Security
Summary Report

51 |
52 |
53 | 54 |
55 |
GitHub Repository:
56 |
{{ github.owner }}/{{ github.repo }}
57 | 58 |
Generated:
59 |
{{ metadata.created }}
60 | 61 |
62 | 63 | {% if sca %} 64 |
65 |
66 |
67 |

Software Composition Analysis

68 |
69 |
70 | 71 |
72 |
73 |
74 |
75 |
Dependencies
76 |
{{ sca.dependencies.totalDependencies }}
77 |
78 | 79 |
80 | 83 | 87 |
88 |
89 |
90 | 91 | {% if sca.vulnerabilities %} 92 |
93 |
94 |
95 |
Dependency Vulnerabilities
96 |
{{ sca.vulnerabilities.total }}
97 |
98 | 99 |
100 | 103 | 106 | 109 | 112 |
113 |
114 |
115 | {% endif %} 116 |
117 |
118 | {% endif %} 119 | 120 | {% if scanning %} 121 |
122 |
123 |
124 |

Code Scanning

125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 |
Code Scanning Rules Applied
133 |
{{ scanning.rules | length }}
134 |
135 |
136 |
137 | 138 |
139 |
140 | 141 |
142 |
143 |
144 |
145 |
CWE Coverage
146 |
{{ scanning.cwe.cwes | length }}
147 |
148 |
149 |
150 |
    151 | {% for cwe in scanning.cwe.cwes | sort %} 152 |
  • {{ cwe }}
  • 153 | {% endfor %} 154 |
155 |
156 |
157 |
158 |
159 |
160 | 161 |
162 |
163 | {% if scanning.results.open %} 164 |
165 |
166 |
Open Findings
167 |
{{ scanning.results.open.total }}
168 |
169 |
170 | {% if scanning.results.open.total > 0 %} 171 | 174 | 177 | {% else %} 178 | 179 | {% endif %} 180 |
181 |
182 | {% endif %} 183 |
184 | 185 |
186 | {% if scanning.results.closed %} 187 |
188 |
189 |
Closed Findings
190 |
{{ scanning.results.closed.total }}
191 |
192 |
193 | {% if scanning.results.closed.total > 0 %} 194 | 197 | 200 | {% else %} 201 | 202 | {% endif %} 203 |
204 |
205 | {% endif %} 206 |
207 |
208 |
209 | {% endif %} 210 |
211 | 212 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /templates/summary_old.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Advanced Security Summary 6 | 7 | 8 | 9 |

GitHub Advanced Security Summary

10 | 11 |

Software Composition Analysis (SCA)

12 | 13 |

Number of Dependencies: {{dependencies.deps.totalDependencies}}

14 | 15 |

SCA Dependency Vulnerability Summary:

16 |

17 |

    18 |
  • Critical: {{dependencies.vulnerabilities.CRITICAL.length}}
  • 19 |
  • High: {{dependencies.vulnerabilities.HIGH.length}}
  • 20 |
  • Moderate: {{dependencies.vulnerabilities.MODERATE.length}}
  • 21 |
  • Low: {{dependencies.vulnerabilities.LOW.length}}
  • 22 |
23 |

24 | 25 |

Code Scaning

26 |

27 | Total CWE Vulnerabilities Checked: {{codeScanning.cwes.length}} 28 | 29 |

30 | {{#each codeScanning.cwes}} 31 | {{this}} 32 | {{/each}} 33 |
34 | 35 |

36 | 37 |

Open Violations

38 |
    39 |
  • Errors: {{codeScanning.open.error.length}}
  • 40 |
  • Warnings: {{codeScanning.open.warning.length}}
  • 41 |
42 | 43 |

Errors

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {{#each codeScanning.open.error}} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {{/each}} 63 |
ToolNameCreatedTagsCWELink
{{this.tool}}{{this.name}}{{this.created}}{{this.rule.details.tags}}{{this.rule.details.cwes}}{{this.url}}
64 | 65 |

Warnings

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {{#each codeScanning.open.warning}} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {{/each}} 85 |
ToolNameCreatedTagsCWELink
{{this.tool}}{{this.name}}{{this.created}}{{this.rule.tags}}{{this.rule.cwes}}{{this.url}}
86 | 87 |

Dependencies Detail

88 | 89 |

Vulnerabilities

90 | 91 | {{#if dependencies.vulnerabilities.CRITICAL.length}} 92 |

Critical

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | {{#each dependencies.vulnerabilities.CRITICAL}} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | {{/each}} 114 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
115 | {{/if}} 116 | 117 | {{#if dependencies.vulnerabilities.HIGH.length}} 118 |

High

119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | {{#each dependencies.vulnerabilities.HIGH}} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | {{/each}} 140 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
141 | {{/if}} 142 | 143 | {{#if dependencies.vulnerabilities.LOW.length}} 144 |

Low

145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | {{#each dependencies.vulnerabilities.LOW}} 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | {{/each}} 166 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
167 | {{/if}} 168 | 169 | {{#if dependencies.vulnerabilities.MODERATE.length}} 170 |

Moderate

171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | {{#each dependencies.vulnerabilities.MODERATE}} 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | {{/each}} 192 |
PackageEcosystemVersionManifestSeverityAdvisory IdAdvisory Summary
{{this._data.securityVulnerability.package.name}}{{this._data.securityVulnerability.package.ecosystem}}{{this._data.vulnerableRequirements}}{{this._data.vulnerableManifestPath}}{{this._data.securityVulnerability.severity}}{{this._data.securityAdvisory.ghsaId}}{{this._data.securityAdvisory.summary}}
193 | {{/if}} 194 | 195 |

Detected Dependencies

196 |

197 | Maven Dependencies: 198 | 199 | 200 | {{#each dependencies.deps.dependencies.maven}} 201 | 202 | 203 | 204 | 205 | {{/each}} 206 |
{{this.name}}{{this.version}}
207 |

208 |

209 | NPM Dependencies: 210 | 211 | 212 | {{#each dependencies.deps.dependencies.npm}} 213 | 214 | 215 | 216 | 217 | {{/each}} 218 |
{{this.name}}{{this.version}}
219 |

220 | 221 | 222 | -------------------------------------------------------------------------------- /templates/vulnerability.html: -------------------------------------------------------------------------------- 1 | {% for severity, vulnerabilities in sca.vulnerabilities %} 2 |
3 |

{{ severity }}

4 |
    5 | {% for vuln in vulnerabilities %} 6 |
  • 7 |
    8 |
    {{ vuln.advisory.summary }}
    9 |
    {{ vuln.created }}
    10 |
    {{ vuln.published }}
    11 |
    12 | {% for identifier in vuln.advisory.identifiers %} 13 |
    {{ identifier.value }}
    14 | {% endfor %} 15 |
    {{ vuln.advisory.permalink }}
    16 |
    {{ vuln.advisory.description }}
    17 |
    18 |
    19 |
  • 20 | {% endfor %} 21 |
22 |
23 | {% endfor %} 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./lib", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "noImplicitAny": false, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "**/*.test.ts" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------