Public Access
Release v1.0.0
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 80
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
dist
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"prettier",
|
||||||
|
"prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
custom: https://svartalf.info/donate/
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
name: Continuous integration
|
||||||
|
|
||||||
|
on: [pull_request, push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create npm configuration
|
||||||
|
run: echo "//npm.pkg.github.com/:_authToken=${token}" >> ~/.npmrc
|
||||||
|
env:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run lint
|
||||||
|
- run: npm run build
|
||||||
|
- run: npm run test
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
__tests__/runner/*
|
||||||
|
|
||||||
|
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Problem matcher which will highlight warnings and errors in the cargo output
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Use `@action-rs/core` package for cargo/cross execution
|
||||||
|
|
||||||
|
## [1.0.1] - 2019-09-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- First public version
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019 actions-rs team and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -1 +1,77 @@
|
|||||||
[WIP]
|
# Rust `audit-check` Action
|
||||||
|
|
||||||
|

|
||||||
|
[](https://gitter.im/actions-rs/community)
|
||||||
|
|
||||||
|
> Security vulnerabilities audit
|
||||||
|
|
||||||
|
This GitHub Action is using [cargo-audit](https://github.com/RustSec/cargo-audit)
|
||||||
|
to perform an audit for crates with security vulnerabilities.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Audit changes
|
||||||
|
|
||||||
|
We can utilize the GitHub Actions ability to execute workflow
|
||||||
|
only if [specific files were changed](https://help.github.com/en/articles/workflow-syntax-for-github-actions#onpushpull_requestpaths)
|
||||||
|
and execute this Action to check the changed dependencies only:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Security audit
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**/Cargo.toml'
|
||||||
|
- '**/Cargo.lock'
|
||||||
|
jobs:
|
||||||
|
security_audit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- uses: actions-rs/audit-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
In that case this Action will create a Check with the advisories found:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Limitations
|
||||||
|
|
||||||
|
Due to [token permissions](https://help.github.com/en/articles/virtual-environments-for-github-actions#token-permissions),
|
||||||
|
this Action **WILL NOT** be able to create Checks for Pull Requests from the forked repositories,
|
||||||
|
see [actions-rs/clippy-check#2](https://github.com/actions-rs/clippy-check/issues/2) for details.\
|
||||||
|
As a fallback this Action will output all advisories found to the stdout.
|
||||||
|
|
||||||
|
## Scheduled audit
|
||||||
|
|
||||||
|
Another option is to use [`schedule`](https://help.github.com/en/articles/events-that-trigger-workflows#scheduled-events-schedule) event
|
||||||
|
and execute this Action periodically against the repository default branch `HEAD`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Security audit
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
jobs:
|
||||||
|
audit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- uses: actions-rs/audit-check@alpha
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
With this workflow Action will be executed at midnight on each day
|
||||||
|
and check if there any new advisories appear for crate dependencies.\
|
||||||
|
For each such advisory an issue will be created:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
| Name | Required | Description | Type | Default |
|
||||||
|
| ------------| -------- | -------------------------------------------------------------------------| ------ | --------|
|
||||||
|
| `token` | ✓ | GitHub token, `${{ secrets.GITHUB_TOKEN }}` | string | |
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
describe('actions-rs/audit', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should do something', async () => {
|
||||||
|
});
|
||||||
|
});
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
name: 'rust-audit-check'
|
||||||
|
description: 'Run cargo audit and check for security advisories'
|
||||||
|
author: 'actions-rs team'
|
||||||
|
branding:
|
||||||
|
icon: play-circle
|
||||||
|
color: black
|
||||||
|
inputs:
|
||||||
|
token:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: 'node12'
|
||||||
|
main: 'dist/index.js'
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
|||||||
|
module.exports = {
|
||||||
|
clearMocks: true,
|
||||||
|
moduleFileExtensions: ['js', 'ts'],
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testMatch: ['**/*.test.ts'],
|
||||||
|
testRunner: 'jest-circus/runner',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': 'ts-jest'
|
||||||
|
},
|
||||||
|
verbose: true
|
||||||
|
}
|
||||||
Generated
+5885
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "rust-audit-check",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": false,
|
||||||
|
"description": "Security audit for security vulnerabilities",
|
||||||
|
"main": "lib/main.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "__tests__"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "ncc build src/main.ts --minify",
|
||||||
|
"watch": "ncc build src/main.ts --watch --minify",
|
||||||
|
"test": "jest",
|
||||||
|
"format": "prettier --write 'src/**/*.{js,ts,tsx}'",
|
||||||
|
"refresh": "rm -rf ./lib/* && npm run-script build",
|
||||||
|
"lint": "tsc --noEmit && eslint 'src/**/*.{js,ts,tsx}'"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions-rs/audit.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"actions",
|
||||||
|
"rust",
|
||||||
|
"cargo",
|
||||||
|
"audit",
|
||||||
|
"security",
|
||||||
|
"advisory"
|
||||||
|
],
|
||||||
|
"author": "actions-rs",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/actions-rs/audit-check/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions-rs/core": "0.0.5",
|
||||||
|
"@actions/core": "^1.1.1",
|
||||||
|
"@actions/github": "^1.1.0",
|
||||||
|
"handlebars": "^4.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.3.2",
|
||||||
|
"ts-node": "^8.4.1",
|
||||||
|
"@typescript-eslint/parser": "^2.3.2",
|
||||||
|
"eslint": "^6.5.1",
|
||||||
|
"eslint-config-prettier": "^6.3.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.1",
|
||||||
|
"@types/jest": "^24.0.13",
|
||||||
|
"@types/node": "^12.7.8",
|
||||||
|
"@zeit/ncc": "^0.20.5",
|
||||||
|
"jest": "^24.9.0",
|
||||||
|
"jest-circus": "^24.9.0",
|
||||||
|
"ts-jest": "^24.1.0",
|
||||||
|
"typescript": "^3.5.1",
|
||||||
|
"prettier": "^1.18.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Parse action input into a some proper thing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { input } from '@actions-rs/core';
|
||||||
|
|
||||||
|
// Parsed action input
|
||||||
|
export interface Input {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get(): Input {
|
||||||
|
return {
|
||||||
|
token: input.getInput('token', { required: true }),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export interface Report {
|
||||||
|
database: DatabaseInfo;
|
||||||
|
lockfile: LockfileInfo;
|
||||||
|
vulnerabilities: VulnerabilitiesInfo;
|
||||||
|
warnings: Vulnerability[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseInfo {
|
||||||
|
'advisory-count': number;
|
||||||
|
'last-commit': string;
|
||||||
|
'last-updated': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LockfileInfo {
|
||||||
|
'dependency-count': number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VulnerabilitiesInfo {
|
||||||
|
found: boolean;
|
||||||
|
count: number;
|
||||||
|
list: Vulnerability[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Vulnerability {
|
||||||
|
advisory: Advisory;
|
||||||
|
package: Package;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Advisory {
|
||||||
|
id: string;
|
||||||
|
package: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
informational: undefined | string | 'notice' | 'unmaintained';
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Package {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
import * as process from 'process';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import * as github from '@actions/github';
|
||||||
|
|
||||||
|
import { Cargo } from '@actions-rs/core';
|
||||||
|
|
||||||
|
import * as input from './input';
|
||||||
|
import * as interfaces from './interfaces';
|
||||||
|
import * as reporter from './reporter';
|
||||||
|
|
||||||
|
const pkg = require('../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
|
|
||||||
|
const USER_AGENT = `${pkg.name}/${pkg.version} (${pkg.bugs.url})`;
|
||||||
|
|
||||||
|
async function getData(): Promise<interfaces.Report> {
|
||||||
|
const cargo = await Cargo.get();
|
||||||
|
await cargo.findOrInstall('cargo-audit');
|
||||||
|
|
||||||
|
await cargo.call(['generate-lockfile']);
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
try {
|
||||||
|
core.startGroup('Calling cargo-audit (JSON output)');
|
||||||
|
await cargo.call(['audit', '--json'], {
|
||||||
|
ignoreReturnCode: true,
|
||||||
|
listeners: {
|
||||||
|
stdout: buffer => {
|
||||||
|
stdout += buffer.toString();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
// Cool story: `cargo-audit` JSON output is missing the trailing `\n`,
|
||||||
|
// so the `::endgroup::` annotation from the line below is being
|
||||||
|
// eaten by it.
|
||||||
|
// Manually writing the `\n` to denote the `cargo-audit` end
|
||||||
|
process.stdout.write(os.EOL);
|
||||||
|
core.endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function run(actionInput: input.Input): Promise<void> {
|
||||||
|
const report = await getData();
|
||||||
|
let shouldReport = false;
|
||||||
|
if (!report.vulnerabilities.found) {
|
||||||
|
core.info('No vulnerabilities were found');
|
||||||
|
} else {
|
||||||
|
core.warning(`${report.vulnerabilities.count} vulnerabilities found!`);
|
||||||
|
shouldReport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.warnings.length === 0) {
|
||||||
|
core.info('No warnings were found');
|
||||||
|
} else {
|
||||||
|
core.warning(`${report.warnings.length} warnings found!`);
|
||||||
|
shouldReport = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldReport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new github.GitHub(actionInput.token, {
|
||||||
|
userAgent: USER_AGENT,
|
||||||
|
});
|
||||||
|
const advisories = report.vulnerabilities.list.concat(report.warnings);
|
||||||
|
if (github.context.eventName == 'schedule') {
|
||||||
|
core.debug(
|
||||||
|
'Action was triggered on a schedule event, creating an Issues report',
|
||||||
|
);
|
||||||
|
await reporter.reportIssues(client, advisories);
|
||||||
|
} else {
|
||||||
|
core.debug(
|
||||||
|
`Action was triggered on a ${github.context.eventName} event, creating a Check report`,
|
||||||
|
);
|
||||||
|
await reporter.reportCheck(client, advisories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const actionInput = input.get();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(actionInput);
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
import * as process from 'process';
|
||||||
|
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import * as github from '@actions/github';
|
||||||
|
|
||||||
|
import { checks } from '@actions-rs/core';
|
||||||
|
import * as interfaces from './interfaces';
|
||||||
|
import * as templates from './templates';
|
||||||
|
|
||||||
|
interface Stats {
|
||||||
|
critical: number;
|
||||||
|
notices: number;
|
||||||
|
unmaintained: number;
|
||||||
|
other: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dumpVulnerabilities(
|
||||||
|
vulnerabilities: Array<interfaces.Vulnerability>,
|
||||||
|
): void {
|
||||||
|
const render = templates.CHECK_TEXT({
|
||||||
|
vulnerabilities: vulnerabilities,
|
||||||
|
});
|
||||||
|
|
||||||
|
core.info(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function plural(value: number, suffix = 's'): string {
|
||||||
|
return value == 1 ? '' : suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStats(vulnerabilities: Array<interfaces.Vulnerability>): Stats {
|
||||||
|
let critical = 0;
|
||||||
|
let notices = 0;
|
||||||
|
let unmaintained = 0;
|
||||||
|
let other = 0;
|
||||||
|
for (const vulnerability of vulnerabilities) {
|
||||||
|
switch (vulnerability.advisory.informational) {
|
||||||
|
case 'notice':
|
||||||
|
notices += 1;
|
||||||
|
break;
|
||||||
|
case 'unmaintained':
|
||||||
|
unmaintained += 1;
|
||||||
|
break;
|
||||||
|
case null:
|
||||||
|
critical += 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
other += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
critical: critical,
|
||||||
|
notices: notices,
|
||||||
|
unmaintained: unmaintained,
|
||||||
|
other: other,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSummary(stats: Stats): string {
|
||||||
|
const blocks: string[] = [];
|
||||||
|
|
||||||
|
if (stats.critical > 0) {
|
||||||
|
// TODO: Plural
|
||||||
|
blocks.push(`${stats.critical} advisory(ies)`);
|
||||||
|
}
|
||||||
|
if (stats.notices > 0) {
|
||||||
|
blocks.push(`${stats.notices} notice${plural(stats.notices)}`);
|
||||||
|
}
|
||||||
|
if (stats.unmaintained > 0) {
|
||||||
|
blocks.push(`${stats.unmaintained} unmaintained`);
|
||||||
|
}
|
||||||
|
if (stats.other > 0) {
|
||||||
|
blocks.push(`${stats.other} other`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and publish audit results into the Commit Check.
|
||||||
|
export async function reportCheck(
|
||||||
|
client: github.GitHub,
|
||||||
|
vulnerabilities: Array<interfaces.Vulnerability>,
|
||||||
|
): Promise<void> {
|
||||||
|
const reporter = new checks.CheckReporter(client, 'Security audit');
|
||||||
|
const stats = getStats(vulnerabilities);
|
||||||
|
const summary = getSummary(stats);
|
||||||
|
|
||||||
|
core.info(`Found ${summary}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await reporter.startCheck('queued');
|
||||||
|
} catch (error) {
|
||||||
|
// `GITHUB_HEAD_REF` is set only for forked repos,
|
||||||
|
// so we can check if it is a fork and not a base repo.
|
||||||
|
if (process.env.GITHUB_HEAD_REF) {
|
||||||
|
core.error(`Unable to publish audit check! Reason: ${error}`);
|
||||||
|
core.warning(
|
||||||
|
'It seems that this Action is executed from the forked repository.',
|
||||||
|
);
|
||||||
|
core.warning(`GitHub Actions are not allowed to use Check API, \
|
||||||
|
when executed for a forked repos. \
|
||||||
|
See https://github.com/actions-rs/clippy-check/issues/2 for details.`);
|
||||||
|
core.info('Posting audit report here instead.');
|
||||||
|
|
||||||
|
dumpVulnerabilities(vulnerabilities);
|
||||||
|
if (stats.critical > 0) {
|
||||||
|
throw new Error(
|
||||||
|
'Critical vulnerabilities were found, marking check as failed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = {
|
||||||
|
title: 'Security advisories found',
|
||||||
|
summary: summary,
|
||||||
|
text: templates.CHECK_TEXT({
|
||||||
|
vulnerabilities: vulnerabilities,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const status = stats.critical > 0 ? 'failure' : 'success';
|
||||||
|
await reporter.finishCheck(status, output);
|
||||||
|
} catch (error) {
|
||||||
|
await reporter.cancelCheck();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.critical > 0) {
|
||||||
|
throw new Error(
|
||||||
|
'Critical vulnerabilities were found, marking check as failed',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reportIssues(
|
||||||
|
client: github.GitHub,
|
||||||
|
vulnerabilities: Array<interfaces.Vulnerability>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { owner, repo } = github.context.repo;
|
||||||
|
for (const vulnerability of vulnerabilities) {
|
||||||
|
const results = await client.search.issuesAndPullRequests({
|
||||||
|
q: `${vulnerability.advisory.id} in:title repo:${owner}/${repo}`,
|
||||||
|
per_page: 1, // eslint-disable-line @typescript-eslint/camelcase
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.data.total_count > 0) {
|
||||||
|
core.info(
|
||||||
|
`Seems like ${vulnerability.advisory.id} is mentioned already in the issues/PRs, \
|
||||||
|
will not report an issue against it`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issue = await client.issues.create({
|
||||||
|
owner: owner,
|
||||||
|
repo: repo,
|
||||||
|
title: `${vulnerability.advisory.id}: ${vulnerability.advisory.title}`,
|
||||||
|
body: templates.ISSUE_BODY({
|
||||||
|
vulnerability: vulnerability,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
core.info(
|
||||||
|
`Created an issue for ${vulnerability.advisory.id}: ${issue.data.html_url}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import * as Handlebars from 'handlebars';
|
||||||
|
|
||||||
|
export const CHECK_TEXT = Handlebars.compile(
|
||||||
|
`
|
||||||
|
{{#each vulnerabilities}}
|
||||||
|
## [{{this.advisory.id}}](https://rustsec.org/advisories/{{this.advisory.id}}.html)
|
||||||
|
|
||||||
|
> {{this.advisory.title}}
|
||||||
|
|
||||||
|
| Details | |
|
||||||
|
| ------------------- | ---------------------------------------------- |
|
||||||
|
{{#if this.advisory.informational}}
|
||||||
|
| Status | {{this.advisory.informational}} |
|
||||||
|
{{/if}}
|
||||||
|
| Package | \`{{this.package.name}}\` |
|
||||||
|
| Version | \`{{this.package.version}}\` |
|
||||||
|
| URL | [{{this.advisory.url}}]({{this.advisory.url}}) |
|
||||||
|
| Date | {{this.advisory.date}} |
|
||||||
|
{{#if this.versions.patched.length}}
|
||||||
|
| Patched versions | \`{{this.versions.patched}}\` |
|
||||||
|
{{/if}}
|
||||||
|
{{#if this.versions.unaffected.length}}
|
||||||
|
| Unaffected versions | \`{{this.versions.unaffected}}\` |
|
||||||
|
{{/if}}
|
||||||
|
{{this.advisory.description}}
|
||||||
|
{{/each}}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
knownHelpersOnly: true,
|
||||||
|
noEscape: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ISSUE_BODY = Handlebars.compile(
|
||||||
|
`
|
||||||
|
> {{vulnerability.advisory.title}}
|
||||||
|
|
||||||
|
| Details | |
|
||||||
|
| ------------------- | ---------------------------------------------- |
|
||||||
|
{{#if vulnerability.advisory.informational}}
|
||||||
|
| Status | {{vulnerability.advisory.informational}} |
|
||||||
|
{{/if}}
|
||||||
|
| Package | \`{{vulnerability.package.name}}\` |
|
||||||
|
| Version | \`{{vulnerability.package.version}}\` |
|
||||||
|
| URL | [{{vulnerability.advisory.url}}]({{vulnerability.advisory.url}}) |
|
||||||
|
| Date | {{vulnerability.advisory.date}} |
|
||||||
|
{{#if vulnerability.versions.patched}}
|
||||||
|
| Patched versions | \`{{vulnerability.versions.patched}}\` |
|
||||||
|
{{/if}}
|
||||||
|
{{#if vulnerability.versions.unaffected}}
|
||||||
|
| Unaffected versions | \`{{vulnerability.versions.unaffected}}\` |
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{vulnerability.advisory.description}}
|
||||||
|
|
||||||
|
See [advisory page](https://rustsec.org/advisories/{{vulnerability.advisory.id}}.html) for additional details.
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
knownHelpersOnly: true,
|
||||||
|
noEscape: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"newLine": "LF",
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noErrorTruncation": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
// TODO: enabling it breaks the `@actions/github` package somehow
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"pretty": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": false,
|
||||||
|
"target": "es2018"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user