├── .commitlintrc.json ├── .github ├── img │ ├── issue.png │ └── report.png └── workflows │ └── code_health.yml ├── .gitignore ├── .husky ├── commit-msg ├── post-merge ├── pre-commit └── pre-push ├── .nvmrc ├── .versionrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.md ├── __fixtures__ └── index.js ├── __tests__ ├── __snapshots__ │ └── utils.test.js.snap └── utils.test.js ├── action.yml ├── dist ├── index.js ├── issue.ejs └── report.ejs ├── jest.config.js ├── package-lock.json ├── package.json ├── schemas ├── database.json └── scope.json ├── src ├── action.js ├── index.js └── utils.js └── templates ├── issue.ejs └── report.ejs /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } -------------------------------------------------------------------------------- /.github/img/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossf/scorecard-monitor/a3a9c4cfa0684480ec5f86fa178fc22c4394b69e/.github/img/issue.png -------------------------------------------------------------------------------- /.github/img/report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ossf/scorecard-monitor/a3a9c4cfa0684480ec5f86fa178fc22c4394b69e/.github/img/report.png -------------------------------------------------------------------------------- /.github/workflows/code_health.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - main 5 | push: 6 | branches: 7 | - main 8 | 9 | name: Source Code Health 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: '20.11.0' 19 | cache: 'npm' 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | - name: Lint 25 | run: npm run lint 26 | 27 | - name: Test 28 | run: npm run test:coverage 29 | 30 | - name: Build 31 | run: npm run build 32 | 33 | - name: Check dist directory 34 | run: | 35 | FILES=$(git status -s dist/) 36 | if [ -n "$FILES" ]; then 37 | echo "You need to include the changes in the dist folder, execute: npm run build and commit the changes in the dist folder" 38 | echo "Uncommitted files:" 39 | echo "$FILES" 40 | echo "Diff content:" 41 | git diff dist/ 42 | exit 1 43 | fi 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,windows,macos,linux,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### macOS ### 20 | # General 21 | .DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | 48 | ### macOS Patch ### 49 | # iCloud generated files 50 | *.icloud 51 | 52 | ### Node ### 53 | # Logs 54 | logs 55 | *.log 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | lerna-debug.log* 60 | .pnpm-debug.log* 61 | 62 | # Diagnostic reports (https://nodejs.org/api/report.html) 63 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 64 | 65 | # Runtime data 66 | pids 67 | *.pid 68 | *.seed 69 | *.pid.lock 70 | 71 | # Directory for instrumented libs generated by jscoverage/JSCover 72 | lib-cov 73 | 74 | # Coverage directory used by tools like istanbul 75 | coverage 76 | *.lcov 77 | 78 | # nyc test coverage 79 | .nyc_output 80 | 81 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 82 | .grunt 83 | 84 | # Bower dependency directory (https://bower.io/) 85 | bower_components 86 | 87 | # node-waf configuration 88 | .lock-wscript 89 | 90 | # Compiled binary addons (https://nodejs.org/api/addons.html) 91 | build/Release 92 | 93 | # Dependency directories 94 | node_modules/ 95 | jspm_packages/ 96 | 97 | # Snowpack dependency directory (https://snowpack.dev/) 98 | web_modules/ 99 | 100 | # TypeScript cache 101 | *.tsbuildinfo 102 | 103 | # Optional npm cache directory 104 | .npm 105 | 106 | # Optional eslint cache 107 | .eslintcache 108 | 109 | # Optional stylelint cache 110 | .stylelintcache 111 | 112 | # Microbundle cache 113 | .rpt2_cache/ 114 | .rts2_cache_cjs/ 115 | .rts2_cache_es/ 116 | .rts2_cache_umd/ 117 | 118 | # Optional REPL history 119 | .node_repl_history 120 | 121 | # Output of 'npm pack' 122 | *.tgz 123 | 124 | # Yarn Integrity file 125 | .yarn-integrity 126 | 127 | # dotenv environment variable files 128 | .env 129 | .env.development.local 130 | .env.test.local 131 | .env.production.local 132 | .env.local 133 | 134 | # parcel-bundler cache (https://parceljs.org/) 135 | .cache 136 | .parcel-cache 137 | 138 | # Next.js build output 139 | .next 140 | out 141 | 142 | # Nuxt.js build / generate output 143 | .nuxt 144 | dist 145 | 146 | # Gatsby files 147 | .cache/ 148 | # Comment in the public line in if your project uses Gatsby and not Next.js 149 | # https://nextjs.org/blog/next-9-1#public-directory-support 150 | # public 151 | 152 | # vuepress build output 153 | .vuepress/dist 154 | 155 | # vuepress v2.x temp and cache directory 156 | .temp 157 | 158 | # Docusaurus cache and generated files 159 | .docusaurus 160 | 161 | # Serverless directories 162 | .serverless/ 163 | 164 | # FuseBox cache 165 | .fusebox/ 166 | 167 | # DynamoDB Local files 168 | .dynamodb/ 169 | 170 | # TernJS port file 171 | .tern-port 172 | 173 | # Stores VSCode versions used for testing VSCode extensions 174 | .vscode-test 175 | 176 | # yarn v2 177 | .yarn/cache 178 | .yarn/unplugged 179 | .yarn/build-state.yml 180 | .yarn/install-state.gz 181 | .pnp.* 182 | 183 | ### Node Patch ### 184 | # Serverless Webpack directories 185 | .webpack/ 186 | 187 | # Optional stylelint cache 188 | 189 | # SvelteKit build / generate output 190 | .svelte-kit 191 | 192 | ### VisualStudioCode ### 193 | .vscode/* 194 | !.vscode/settings.json 195 | !.vscode/tasks.json 196 | !.vscode/launch.json 197 | !.vscode/extensions.json 198 | !.vscode/*.code-snippets 199 | 200 | # Local History for Visual Studio Code 201 | .history/ 202 | 203 | # Built Visual Studio Code Extensions 204 | *.vsix 205 | 206 | ### VisualStudioCode Patch ### 207 | # Ignore all local history of files 208 | .history 209 | .ionide 210 | 211 | ### Windows ### 212 | # Windows thumbnail cache files 213 | Thumbs.db 214 | Thumbs.db:encryptable 215 | ehthumbs.db 216 | ehthumbs_vista.db 217 | 218 | # Dump file 219 | *.stackdump 220 | 221 | # Folder config file 222 | [Dd]esktop.ini 223 | 224 | # Recycle Bin used on file shares 225 | $RECYCLE.BIN/ 226 | 227 | # Windows Installer files 228 | *.cab 229 | *.msi 230 | *.msix 231 | *.msm 232 | *.msp 233 | 234 | # Windows shortcuts 235 | *.lnk 236 | 237 | # End of https://www.toptal.com/developers/gitignore/api/node,windows,macos,linux,visualstudiocode 238 | 239 | .vscode/ -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm i -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run test --coverage -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.11.0 -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | {"type": "feat", "section": "Features"}, 4 | {"type": "fix", "section": "Bug Fixes"}, 5 | {"type": "chore", "section": "Other"}, 6 | {"type": "docs", "section": "Other"}, 7 | {"type": "style", "section": "Other"}, 8 | {"type": "refactor", "section": "Other"}, 9 | {"type": "perf", "section": "Others"}, 10 | {"type": "test", "section": "Test"} 11 | ], 12 | "commitUrlFormat": "https://github.com/ossf/scorecard-monitor/commits/{{hash}}", 13 | "compareUrlFormat": "https://github.com/ossf/scorecard-monitor/compare/{{previousTag}}...{{currentTag}}" 14 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.0.1](https://github.com/ossf/scorecard-monitor/compare/v1.0.0...v1.0.1) (2023-02-15) 6 | 7 | ### Other 8 | 9 | * added github action branding ([831364a](https://github.com/ossf/scorecard-monitor/commits/831364aaf4654af1bbc4747fcbc414539b9d7a74)) 10 | 11 | ## 1.0.0 (2023-02-15) 12 | 13 | ### Features 14 | 15 | * added basic github action definition ([f98ce7c](https://github.com/ossf/scorecard-monitor/commits/f98ce7c0affd8559f37e5fbf2f9a2c3e30242eca)) 16 | * added basic utilities ([cf3fdb1](https://github.com/ossf/scorecard-monitor/commits/cf3fdb12bd1d8fcdd387d1a9c8a73fc20dcb1c50)) 17 | * added commit changes capability ([6a82bda](https://github.com/ossf/scorecard-monitor/commits/6a82bda3f82787caf58e6ca3cd65002943b36484)) 18 | * added default settings ([1382939](https://github.com/ossf/scorecard-monitor/commits/138293900da4c9b7b9ee9ed7158df420c218e243)) 19 | * added dependency vercel/ncc ([6daf8c8](https://github.com/ossf/scorecard-monitor/commits/6daf8c85e37e89833cc388cfa525658056bc4e12)) 20 | * added Ejs as dependency ([ddb8091](https://github.com/ossf/scorecard-monitor/commits/ddb8091243ae905f3e57374de72a7374d5cf00b2)) 21 | * added empty config file ([0ab5133](https://github.com/ossf/scorecard-monitor/commits/0ab51330c188a6e2e19e8c1705f95e8ec6a9b357)) 22 | * added empty db file ([5d9e236](https://github.com/ossf/scorecard-monitor/commits/5d9e236327ddc4fe9a9819e3280d9738010f14d8)) 23 | * added github action core logic ([324614d](https://github.com/ossf/scorecard-monitor/commits/324614d184fbbf02b034825cf46fb413b8318cfd)) 24 | * added issue template ([71f9bbb](https://github.com/ossf/scorecard-monitor/commits/71f9bbb2901d471c8014495a7e8b885d97ac23f7)) 25 | * added markdown reporting ([c38a0d8](https://github.com/ossf/scorecard-monitor/commits/c38a0d85806d24f82713a94cd7119308d53cef14)) 26 | * added max resquest in settings ([868c4b8](https://github.com/ossf/scorecard-monitor/commits/868c4b86e575e9d4d1356ca9b5a6359b2e03f92a)) 27 | * added notification settings ([2e008f5](https://github.com/ossf/scorecard-monitor/commits/2e008f5e50724b414c6b6c2a71dd58fcb10ad0cd)) 28 | * added octokit/rest library ([f7eacf0](https://github.com/ossf/scorecard-monitor/commits/f7eacf08cba8034723fd539d90a674de36c250a6)) 29 | * extended inputs ([1e80345](https://github.com/ossf/scorecard-monitor/commits/1e80345be1777b7db25952af3d1081af93141a03)) 30 | * simplified legacy code ([eb024ed](https://github.com/ossf/scorecard-monitor/commits/eb024ed84e505433bb89098c65140e43b5784e7c)) 31 | * WIP core logic ([939ebba](https://github.com/ossf/scorecard-monitor/commits/939ebba0c1d773237366620cefd77e2a54afff09)) 32 | 33 | ### Bug Fixes 34 | 35 | * minor bugs ([#4](https://github.com/ossf/scorecard-monitor/issues/4)) ([e07d7f7](https://github.com/ossf/scorecard-monitor/commits/e07d7f7dd38efa5caaf64d0c548752f2ae54ed45)) 36 | 37 | ### Other 38 | 39 | * added actions dependencies ([42029e2](https://github.com/ossf/scorecard-monitor/commits/42029e2788dfae7ad0b2ffd9011b085d46b10416)) 40 | * added basic documentation ([ca9d057](https://github.com/ossf/scorecard-monitor/commits/ca9d0574a6189c0244abc2fa1951f2f3fb0ca265)) 41 | * added build step ([edfbc40](https://github.com/ossf/scorecard-monitor/commits/edfbc40e1c79bb3317e54f3b1265d762df1bf248)) 42 | * added CI pipeline ([14cc3ef](https://github.com/ossf/scorecard-monitor/commits/14cc3ef72671b56fc196639319ef1727af7b5f8b)) 43 | * added dependency ([84bf461](https://github.com/ossf/scorecard-monitor/commits/84bf4617d82095b9c11b58ed528cd0e1b19679d7)) 44 | * added dev dependencies ([61fb9df](https://github.com/ossf/scorecard-monitor/commits/61fb9dfdb9f58ec422c917c0fe7be82cea8cbed7)) 45 | * added dist folder ([b51795a](https://github.com/ossf/scorecard-monitor/commits/b51795a3eb45c7ed30854d42c0f71baf2e800f98)) 46 | * added extra references ([a04f4d0](https://github.com/ossf/scorecard-monitor/commits/a04f4d0b7d3acdee274d9dc7f7eb7ba9cdd728b4)) 47 | * added husky git hooks ([cf56817](https://github.com/ossf/scorecard-monitor/commits/cf568176151265c092051ce11de5fdbbeaa822ee)) 48 | * added initial file ([7ef1880](https://github.com/ossf/scorecard-monitor/commits/7ef18801e6ef662e8b345bcf1afb7272843eec93)) 49 | * added linting ([b1ba183](https://github.com/ossf/scorecard-monitor/commits/b1ba1837c9b79d133cd8eae18779eb95706b4bb1)) 50 | * added nodejs engine specs ([fc35159](https://github.com/ossf/scorecard-monitor/commits/fc351595aeb23903ea54406d91f8c41b0d1601c8)) 51 | * added test support ([60c3763](https://github.com/ossf/scorecard-monitor/commits/60c3763c9e9ce21708e0345e941536e5b94a24d0)) 52 | * avoid npm publication ([33aca47](https://github.com/ossf/scorecard-monitor/commits/33aca4797de7acb581b37ae3885e64ca33d7dadd)) 53 | * dist added ([afcf7a1](https://github.com/ossf/scorecard-monitor/commits/afcf7a1898698eb4aa5be176dbffb58cd92c68cb)) 54 | * downgrade to got v11 ([47fa4da](https://github.com/ossf/scorecard-monitor/commits/47fa4dab2892369999a824dfde018361942efbcd)) 55 | * downgrade to Node v16 ([006443a](https://github.com/ossf/scorecard-monitor/commits/006443a765e291856bce17e6def903a22d26f7d9)) 56 | * extended releases config ([3ca4134](https://github.com/ossf/scorecard-monitor/commits/3ca4134c7682bc12cb95b0fe7c698caf16514af2)) 57 | * extended releases config ([0668780](https://github.com/ossf/scorecard-monitor/commits/06687803ea166c19c13a2cfe9259ba4d051943d0)) 58 | * ignored dist folder for linting ([28e8390](https://github.com/ossf/scorecard-monitor/commits/28e83905706f9a0de7b11655a0e5fbf9271d98e2)) 59 | * improved documentation ([22bd265](https://github.com/ossf/scorecard-monitor/commits/22bd265c0374c79d8334d36f9be5ee88b744439c)) 60 | * linting ([a166a68](https://github.com/ossf/scorecard-monitor/commits/a166a682889bcc757ff6ab9f330eae3c8b546132)) 61 | * linting ([e889af3](https://github.com/ossf/scorecard-monitor/commits/e889af3c39fdf3cc3c8d74819442ea043d6a4291)) 62 | * linting ([ed33e9a](https://github.com/ossf/scorecard-monitor/commits/ed33e9afe096a156e9e7ff9580c037f945b1d613)) 63 | * npm initialization ([fb2613b](https://github.com/ossf/scorecard-monitor/commits/fb2613b272128f4151ea8eb57f2b3aa310df514b)) 64 | * removed md extension from template location ([7be296d](https://github.com/ossf/scorecard-monitor/commits/7be296d134fc436c9c2b151149ce4a6a58483570)) 65 | * removed reference files ([918708a](https://github.com/ossf/scorecard-monitor/commits/918708a98feff0054ad7d68f17ae534a8cb354bd)) 66 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to OpenSSF Scorecard Monitor 2 | 3 | Thank you for contributing your time and expertise to the OpenSSF Scorecard Monitor 4 | project. This document describes the contribution guidelines for the project. 5 | 6 | > [!IMPORTANT] 7 | > Before you start contributing, you must read and abide by our 8 | **[Code of Conduct](https://github.com/ossf/scorecard-monitor?tab=coc-ov-file)**. 9 | > 10 | > Additionally, the Linux Foundation (LF) requires all contributions include per-commit sign-offs. 11 | > Ensure you use the `-s` or `--signoff` flag for every commit. 12 | > 13 | > For more details, see the [LF DCO wiki](https://wiki.linuxfoundation.org/dco) 14 | > or [this Pi-hole signoff guide](https://docs.pi-hole.net/guides/github/how-to-signoff/). 15 | 16 | - [Contributing code](#contributing-code) 17 | - [Getting started](#getting-started) 18 | - [Environment Setup](#environment-setup) 19 | - [New to Node.js?](#new-to-nodejs) 20 | - [Contributing steps](#contributing-steps) 21 | - [Running the project locally](#running-the-project-locally) 22 | - [Installing the project dependencies](#installing-the-project-dependencies) 23 | - [Running tests](#running-tests) 24 | - [Linting the codebase](#linting-the-codebase) 25 | - [What to do before submitting a pull request](#what-to-do-before-submitting-a-pull-request) 26 | - [PR Process](#pr-process) 27 | - [Where the CI Tests are configured](#where-the-ci-tests-are-configured) 28 | - [Updating Docs](#updating-docs) 29 | 30 | ## Contributing code 31 | 32 | ### Getting started 33 | 34 | 1. Create [a GitHub account](https://github.com/join) 35 | 1. Create a [personal access token](https://docs.github.com/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) 36 | 1. Set up your [development environment](#environment-setup) 37 | 38 | ### Environment Setup 39 | 40 | You must install these tools: 41 | 42 | 1. [`git`](https://help.github.com/articles/set-up-git/): For source control 43 | 44 | 1. [`node`](https://nodejs.org/en/download/package-manager): You need node version `v20+`. The project includes support for [nvm](https://github.com/nvm-sh/nvm). 45 | 46 | ### New to Node.js? 47 | 48 | If you're unfamiliar with Node.js, there are plenty of articles, resources, and books. 49 | We recommend starting with several resources from the official Node.js website: 50 | 51 | - [Introduction to Node.js](https://nodejs.org/en/learn/getting-started/introduction-to-nodejs) 52 | 53 | ## Contributing steps 54 | 55 | 1. Identify an existing issue you would like to work on, or submit an issue describing your proposed change to the repo in question. 56 | 1. The maintainers will respond to your issue promptly. 57 | 1. Fork this repo, develop, and test your code changes. 58 | 1. Submit a pull request. 59 | 60 | ## Running the project locally 61 | 62 | Currently, this project is consumed as a GitHub Action, so local development is quite limited. In order to test the full workflow, you can consume directly your fork and branch from another project or by adding a new workflow in your fork. 63 | 64 | Aside from this, it is possible to test certain things locally, like the `utils.js` file. Check the test folder `_tests_/utils.test.js` to get a better idea. 65 | 66 | ## Installing the project dependencies 67 | 68 | First, check that you are using Node v20+ and then execute `npm ci` instead of `npm i` or `npm install` as you want to mimic the pipeline steps in order to avoid discrepancies later on with the `dist/` as the dependencies are included there. 69 | 70 | ## Running tests 71 | 72 | Currently, the project is using [Jest](https://jestjs.io/) and [Snapshot Testing](https://jestjs.io/docs/snapshot-testing). 73 | 74 | You have several options to run the tests: 75 | 76 | - `npm run test`: this will run the tests 77 | - `npm run test:update`: this will run the tests and update the snapshots 78 | - `npm run test:coverage` this will run the tests and generate a coverage report as terminal output and in HTML format that can be found in the `coverage/` folder 79 | - `npm run test:watch`: this will run the tests when you make changes in any of the project's files. 80 | 81 | ## Linting the codebase 82 | 83 | This project uses [JavaScript Standard Style](https://standardjs.com/). If you are not familiar with this style, you can make your changes and run `npm run lint:fix` when you are ready. The linter will fix most of the issues for you and it will highlight any additional issue that requires manual work. 84 | 85 | To check that your files are properly linted, you can run `npm run lint`. This review won't make changes to your files. 86 | 87 | ## What to do before submitting a pull request 88 | 89 | The following are the targets that can be used to test your changes locally: 90 | 91 | | Command | Description | Is called in the CI? | 92 | | -------- | -------------------------------------------------- | -------------------- | 93 | | `npm run lint:fix` | force the JS Standard style on your files | checked as `npm run lint` | 94 | | `npm run test:coverage` | Run the unit tests | Yes | 95 | | `npm run build` | Use ncc to generate the `dist/` folder | yes, it has a validation step | 96 | 97 | ## PR Process 98 | 99 | Every PR should be tagged with the relevant labels. This work is done by the maintainers exclusively. 100 | 101 | Individual commits should not be tagged separately, but will generally be 102 | assumed to match the PR. For instance, if you have a bugfix in with a breaking 103 | change, it's generally encouraged to submit the bugfix separately, but if you must put them in one PR, you should mark the whole PR as breaking. 104 | 105 | > [!NOTE] 106 | > Once a maintainer reviews your code, please address feedback without rebasing when possible. 107 | > This includes [synchronizing your PR](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch) 108 | > with `main`. The GitHub review experience is much nicer with traditional merge commits. 109 | 110 | ## Where the CI Tests are configured 111 | 112 | 1. See the [action files](.github/workflows) to check its tests, and the scripts used on it. 113 | 114 | ## Updating Docs 115 | 116 | The documentation can be found in the [README](./README.md). Any changes that are merged to `main` will be reflected directly on the [GitHub Actions Marketplace](https://github.com/marketplace/actions/openssf-scorecard-monitor), so documentation changes do not require a specific release. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Ulises Gascón 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSSF Scorecard Monitor 2 | 3 | **Simplify OpenSSF Scorecard tracking in your organization with automated markdown and JSON reports, plus optional GitHub issue alerts.** 4 | 5 | *This project is part of [OpenSSF Scorecard](https://github.com/ossf/scorecard). Read [the announcement](https://github.com/ossf/scorecard-monitor/issues/79) for more details.* 6 | 7 | ## 🔮 About 8 | 9 | If you're feeling overwhelmed by an avalanche of scorecards across your organizations, you can breathe easy: automation is here to make your life easier! Scorecard Monitor streamlines the process of keeping track of them all by providing a comprehensive report in Markdown and a local database in JSON with all the scores. To stay on top of any changes in the scores, you can also choose to get notifications through Github Issues. 10 | 11 | ## ✅ Requirements 12 | 13 | Please ensure that any repository you wish to track with Scorecard Monitor has already been analyzed by [OpenSSF Scorecard](https://github.com/ossf/scorecard) at least once. This can be accomplished using the official [GitHub Action](https://github.com/ossf/scorecard-action) or the [Scorecard CLI](https://github.com/ossf/scorecard?tab=readme-ov-file#scorecard-command-line-interface). 14 | 15 | It's also possible that some repositories in your organization are already being [automatically tracked](https://github.com/ossf/scorecard/blob/main/docs/faq.md#can-i-preview-my-projects-score) by OpenSSF in this [CSV file](https://github.com/ossf/scorecard/blob/main/cron/internal/data/projects.csv) via weekly cronjob. One caveat: Automatically tracked projects _do not_ include [certain checks](https://github.com/ossf/scorecard/issues/3438) in their analysis (`CI-Tests,Contributors,Dependency-Update-Tool,Webhooks`). 16 | 17 | If you're not sure whether a specific project is already using OpenSSF Scorecard, you can always spot-check with the following URL pattern: `https://securityscorecards.dev/viewer/?uri=github.com//` (substitute `` and `` as appropriate). The [Scorecard API](https://api.scorecard.dev/) is also able to fetch scores for a given repository. 18 | 19 | ## 📺 Tutorial 20 | 21 | This section is coming soon. 22 | If you would like to contribute to the documentation, please feel free to open a pull request for review. 23 | 24 | ## ❤️ Awesome Features 25 | 26 | - Easy to use with great customization 27 | - Easy to patch the scoring as the reports includes a direct link to [StepSecurity](https://app.stepsecurity.io) 28 | - Easy way to visualize results with [Scorecard Visualizer](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/nodejs/node) or [deps.dev](https://deps.dev/project/github/nodejs%2Fnode) 29 | - Cutting-edge feature that effortlessly compares OpenSSF scorecards between previous and current commits with [The Scorecard Visualizer Comparator](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/nodejs/node/compare/39a08ee8b8d3818677eb823cb566f36b1b1c4671/19fa9f1bc47b0666be0747583bea8cb3d8ad5eb1) 30 | - Discovery mode: list all the repos in one or many organizations that are already being tracked with [OpenSSF Scorecard](https://github.com/ossf/scorecard) 31 | - Reporting in Markdown with essential information (hash, date, score) and comparative against the prior score 32 | - Self-hosted: The reporting data is stored in JSON format (including previous records) in the repo itself 33 | - Generate an issue (assignation, labels..) with the last changes in the scores, including links to the full report 34 | - Automatically create a pull request for repositories that have branch protection enabled 35 | - Easy to exclude/include new repositories in the scope from any GitHub organization 36 | - Extend the markdown template with you own content by using tags 37 | - Easy to modify the files and ensure the integrity with JSON Schemas 38 | - The report data is exported as an output and can be used in the pipeline 39 | - Great test coverage 40 | 41 | ### 🎉 Demo 42 | 43 | #### Sample Report 44 | 45 | ![sample report](.github/img/report.png) 46 | 47 | _[Sample report](https://github.com/nodejs/security-wg/blob/main/tools/ossf_scorecard/report.md)_ 48 | 49 | #### Sample Issue 50 | 51 | ![sample issue preview](.github/img/issue.png) 52 | 53 | _[Sample issue](https://github.com/nodejs/security-wg/issues/885)_ 54 | 55 | ## :shipit: Used By 56 | 57 | - [Nodejs](https://github.com/nodejs): The Node.js Ecosystem Security Working Group is using [this pipeline](https://github.com/nodejs/security-wg/blob/main/.github/workflows/ossf-scorecard-reporting.yml) to generate a [report](https://github.com/nodejs/security-wg/blob/main/tools/ossf_scorecard/report.md) with scores for all the repositories in the Node.js org. 58 | - [One Beyond](https://github.com/onebeyond): The Maintainers are using [this pipeline](https://github.com/onebeyond/maintainers/blob/main/.github/workflows/security-scoring.yml) to generate a scoring report inside [a specific document][onebeyond-report], in order to generate a [web version](https://onebeyond-maintainers.netlify.app/reporting/ossf-scorecard) of it 59 | - [NodeSecure](https://github.com/NodeSecure): The Maintainers are using [this pipeline](https://github.com/NodeSecure/Governance/blob/main/.github/workflows/ossf-scorecard-reporting.yml) to generate a scoring report and notification issues. 60 | - **[More users](https://github.com/ossf/scorecard-monitor/network/dependents)** 61 | 62 | [onebeyond-report]: https://github.com/onebeyond/maintainers/blob/main/docs/06-reporting/01-scorecard.md 63 | 64 | ## 📡 Usage 65 | 66 | ### Standalone with auto-discovery version 67 | 68 | With the following workflow, you will get the most out of this action: 69 | 70 | - Trigger manually or by Cron job every Sunday 71 | - It will scan the org(s) in scope looking for repositories that are available in the OpenSSF Scorecard 72 | - It will store the database and the scope files in the repo 73 | - It will generate an issue if there are changes in the score 74 | 75 | ```yml 76 | name: "OpenSSF Scoring" 77 | on: 78 | # Scheduled trigger 79 | schedule: 80 | # Run every Sunday at 00:00 81 | - cron: "0 0 * * 0" 82 | # Manual trigger 83 | workflow_dispatch: 84 | 85 | permissions: 86 | # Write access in order to update the local files with the reports 87 | contents: write 88 | # Write access only required if creating PRs (see Advanced Tips below) 89 | pull-requests: none 90 | # Write access in order to create issues 91 | issues: write 92 | packages: none 93 | 94 | jobs: 95 | security-scoring: 96 | runs-on: ubuntu-latest 97 | steps: 98 | - uses: actions/checkout@v2 99 | - name: OpenSSF Scorecard Monitor 100 | uses: ossf/scorecard-monitor@v2.0.0-beta8 101 | with: 102 | scope: reporting/scope.json 103 | database: reporting/database.json 104 | report: reporting/openssf-scorecard-report.md 105 | auto-commit: true 106 | auto-push: true 107 | generate-issue: true 108 | # The token is needed to create issues, discovery mode and pushing changes in files 109 | github-token: ${{ secrets.GITHUB_TOKEN }} 110 | discovery-enabled: true 111 | # As an example nodejs Org and Myself 112 | discovery-orgs: 'UlisesGascon,nodejs' 113 | ``` 114 | 115 | ### Options 116 | 117 | - `scope`: Defines the path to the file where the scope is defined 118 | - `database`: Defines the path to the JSON file usage to store the scores and compare 119 | - `report`: Defines the path where the markdown report will be added/updated 120 | - `auto-commit`: Commits the changes in the `database` and `report` files 121 | - `auto-push`: Pushes the code changes to the branch 122 | - `generate-issue`: Creates an issue with the scores that had been updated 123 | - `issue-title`: Defines the issue title 124 | - `issue-assignees`: List of assignees for the issue 125 | - `issue-labels`: List of labels for the issue 126 | - `github-token`: The token usage to create the issue and push the code 127 | - `max-request-in-parallel`: Defines the total HTTP Request that can be done in parallel 128 | - `discovery-enabled`: Defined if the discovery is enabled 129 | - `discovery-orgs`: List of organizations to be includes in the discovery, example: `discovery-orgs: owasp,nodejs`. The OpenSSF Scorecard API is case sensitive, please use the same organization name as in the GitHub url, like: is `NodeSecure` and not `nodesecure`. [See example](https://github.com/NodeSecure/Governance/issues/21#issuecomment-1474770986) 130 | - `report-tags-enabled`: Defines if the markdown report must be created/updated around tags by default is disabled. This is useful if the report is going to be include in a file that has other content on it, like docusaurus docs site or similar 131 | - `report-start-tag` Defines the start tag, default `` 132 | - `report-end-tag`: Defines the closing tag, default `` 133 | - `render-badge`: Defines if the OpenSSF Scorecard badge must be rendered in the reporter to only show the score 134 | - `report-tool`: Defines the reporting review tool in place: `scorecard-visualizer` [Example](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/nodejs/node) or `deps.dev` [Example](https://deps.dev/project/github/nodejs%2Fnode), by default `scorecard-visualizer` 135 | 136 | ### Outputs 137 | 138 | - `scores`: Score data in JSON format 139 | 140 | ```yml 141 | name: "OpenSSF Scoring" 142 | on: 143 | # ... 144 | 145 | permissions: 146 | # ... 147 | 148 | jobs: 149 | security-scoring: 150 | runs-on: ubuntu-latest 151 | steps: 152 | - uses: actions/checkout@v2 153 | - name: OpenSSF Scorecard Monitor 154 | uses: ossf/scorecard-monitor@v2.0.0-beta8 155 | id: openssf-scorecard-monitor 156 | with: 157 | # .... 158 | - name: Print the scores 159 | run: | 160 | echo '${{ steps.openssf-scorecard-monitor.outputs.scores }}' 161 | ``` 162 | 163 | ## 🚀 Advanced Tips 164 | 165 | ### Avoid committing directly to the branch and instead generate a PR 166 | 167 | If you have implemented the recommended branch protection rules from the OpenSSF Scorecard, committing and pushing directly to the `main` branch will be impossible. An easy alternative is to extend the pipeline to automatically generate a PR for you: 168 | 169 | ```yml 170 | name: "OpenSSF Scoring" 171 | on: 172 | # ... 173 | 174 | permissions: 175 | contents: write 176 | pull-requests: write 177 | issues: write 178 | packages: none 179 | 180 | jobs: 181 | security-scoring: 182 | runs-on: ubuntu-latest 183 | steps: 184 | - uses: actions/checkout@v2 185 | - name: OpenSSF Scorecard Monitor 186 | uses: ossf/scorecard-monitor@v2.0.0-beta8 187 | id: openssf-scorecard-monitor 188 | with: 189 | auto-commit: false 190 | auto-push: false 191 | generate-issue: true 192 | # .... 193 | - name: Print the scores 194 | run: | 195 | echo '${{ steps.openssf-scorecard-monitor.outputs.scores }}' 196 | - name: Create Pull Request 197 | uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54 # v4.2.4 198 | with: 199 | token: ${{ secrets.GITHUB_TOKEN }} 200 | commit-message: OpenSSF Scorecard Report Updated 201 | title: OpenSSF Scorecard Report Updated 202 | body: OpenSSF Scorecard Report Updated 203 | base: main 204 | assignees: ${{ github.actor }} 205 | branch: openssf-scorecard-report-updated 206 | delete-branch: true 207 | ``` 208 | 209 | ### Embed Report version 210 | 211 | If you want to mix the report in markdown format with other content, then you can use `report-tags-enabled=true` then report file will use the tags to add/update the report summary without affecting what is before or after the tagged section. 212 | 213 | This is very useful for static websites, here is [an example using docusaurus][onebeyond-report]. 214 | 215 | ### Custom tags 216 | 217 | By default we use `` and ``, but this can be customize by adding your custom tags as `report-start-tag` and `report-end-tag` 218 | 219 | ### Increase HTTP request in parallel 220 | 221 | You can control the amount of parallel requests performed against the OpenSSF Scorecard Api by defining any numerical value in `max-request-in-parallel`, like `max-request-in-parallel=15`. 222 | 223 | By default the value is 10, higher values might not be a good use of the API and you can hit some limits, please check with OpenSSF if you want to rise the limits safely. 224 | 225 | ### Exclude repos 226 | 227 | In some scenarios we want to enable the auto-discovery mode but we want to ignore certain repos, the best way to achieve that is by editing the `scope.json` file and add any report that you want to ignore in the `excluded` section for that specific organization. 228 | 229 | ## 🍿 Other 230 | 231 | ### Scoping Structure 232 | 233 | Just for reference, the scope will be stored this way: 234 | 235 | File: `reporting/scope.json` 236 | 237 | ```json 238 | { 239 | "github.com": { 240 | "included": { 241 | "UlisesGascon":[ 242 | "tor-detect-middleware", 243 | "check-my-headers", 244 | "express-simple-pagination" 245 | ] 246 | }, 247 | "excluded": { 248 | "UlisesGascon": [ 249 | "demo-stuff" 250 | ] 251 | } 252 | } 253 | 254 | } 255 | ``` 256 | 257 | ### Database structure 258 | 259 | Just for reference, the database will store the current value and previous values with the date: 260 | 261 | ```json 262 | { 263 | "github.com": { 264 | "UlisesGascon": { 265 | "check-my-headers": { 266 | "previous": [ { 267 | "score": 6.7, 268 | "date": "2022-08-21" 269 | }], 270 | "current": { 271 | "score": 4.4, 272 | "date": "2022-11-28" 273 | } 274 | } 275 | } 276 | } 277 | } 278 | ``` 279 | 280 | ## 💪 Contributing 281 | 282 | Please read [CONTRIBUTING.md](https://github.com/ossf/scorecard-monitor/blob/main/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests to us. You need to accept DCO 1.1 in order to make contributions. 283 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | This document describes the process for releasing a new version of the Scorecard Monitor. 4 | 5 | ## Changelog and versioning 6 | 7 | **This work is done by the maintainers exclusively.** 8 | 9 | In order to generate a new release, it is recommended to use the commands: 10 | 11 | ```console 12 | npm run release:minor 13 | npm run release:patch 14 | npm run release:major 15 | ``` 16 | 17 | This includes all the changes in the [CHANGELOG](./CHANGELOG.md) and ensures that the `package.json` and `package-lock.json` are up to date. 18 | 19 | You can discard the tag that has been generated locally, as we won't use it. 20 | 21 | ## Releasing a new version 22 | 23 | **This work is done by the maintainers exclusively.** 24 | 25 | It is important to ensure that the `package.json`, `package-lock.json` and `CHANGELOG.md` are correct and include all the details for the new release in the `main` branch. 26 | 27 | In order to create a new release, follow these steps: 28 | 29 | 1. Use the GitHub web UI for [new releases](https://github.com/ossf/scorecard-monitor/releases/new). 30 | 2. Mark `Publish this Action to the GitHub Marketplace` as we want to deliver this to our users. 31 | 3. Target the new release version, like `v.1.0.3-beta5`. Note that you can use metadata like `-beta5` and you must include `v` as prefix. 32 | 4. Mark `Set as the latest release` 33 | 5. (Optionally) mark `Set as a pre-release` if it is non-production ready. 34 | 6. :bulb: **Hint:** You can check another release ([example](https://github.com/ossf/scorecard-monitor/releases/tag/v2.0.0-beta7)) to follow the style for title and description (_Main Changes, PRs, New contributions_). 35 | 7. Click the `Generate release notes` button in the top right to automatically populate the release description. 36 | -------------------------------------------------------------------------------- /__fixtures__/index.js: -------------------------------------------------------------------------------- 1 | const baseDatabase = { 2 | 'github.com': { 3 | UlisesGascon: { 4 | 'security-wg': { 5 | previous: [], 6 | current: { 7 | score: 4.3, 8 | date: '2023-02-20', 9 | commit: '846b3ddb5f75d95235e94d9eb52e920f4a067338' 10 | } 11 | }, 12 | sweetpgp: { 13 | previous: [ 14 | { 15 | score: 1.6, 16 | date: '2002-01-12', 17 | commit: '136da4a72ce9a9b9954e7c009da9f42f3d00fab1' 18 | } 19 | ], 20 | current: { 21 | score: 4.6, 22 | date: '2022-11-28', 23 | commit: 'b2d932467fdc06b11dedf88f17de68e75dc2b11d' 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | const baseScope = { 31 | 'github.com': { 32 | UlisesGascon: { 33 | included: [ 34 | 'browser-redirect', 35 | 'check-my-headers', 36 | 'the-scraping-machine', 37 | 'sweetpgp', 38 | 'tor-detect-middleware' 39 | ], 40 | excluded: [ 41 | 'express-simple-pagination', 42 | 'security-wg' 43 | ] 44 | }, 45 | 'refined-github': { 46 | included: [], 47 | excluded: [] 48 | }, 49 | istanbuljs: { 50 | included: [ 51 | 'babel-plugin-istanbul', 52 | 'nyc', 53 | 'spawn-wrap', 54 | 'eslint-plugin-istanbul' 55 | ], 56 | excluded: [] 57 | } 58 | } 59 | } 60 | 61 | const database = { 62 | fullDatabase: baseDatabase, 63 | emptyDatabase: { 64 | 'github.com': {} 65 | } 66 | } 67 | 68 | const scope = { 69 | fullScope: baseScope, 70 | emptyScope: { 71 | 'github.com': {} 72 | } 73 | } 74 | 75 | const scores = [ 76 | { 77 | org: 'fake-org', 78 | repo: 'fake-repo', 79 | platform: 'github.com', 80 | commit: '846b3ddb5f75d95235e94d9eb52e920f4a067338', 81 | prevCommit: '39a08ee8b8d3818677eb823cb566f36b1b1c4671', 82 | score: 10, 83 | date: '2023-02-20', 84 | currentDiff: 5 85 | } 86 | ] 87 | 88 | module.exports = { 89 | database, 90 | scope, 91 | scores 92 | } 93 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/utils.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Utils generateReportContent Should render template with deps.dev as renderTool 1`] = ` 4 | "# OpenSSF Scorecard Report 5 | 6 | ## Summary 7 | 8 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 9 | | -- | -- | -- | -- | -- | -- | -- | 10 | | [fake-org/fake-repo](https://github.com/fake-org/fake-repo) | [846b3dd](https://github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo/badge)](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo) | 2023-02-20 | 5 / [Details](https://deps.dev/project/github/fake-org%2Ffake-repo) | [View](https://deps.dev/project/github/fake-org%2Ffake-repo) | [Fix it](https://app.stepsecurity.io/securerepo?repo=fake-org/fake-repo) | 11 | 12 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 13 | " 14 | `; 15 | 16 | exports[`Utils generateReportContent Should render template with scorecard-visualizer as renderTool 1`] = ` 17 | "# OpenSSF Scorecard Report 18 | 19 | ## Summary 20 | 21 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 22 | | -- | -- | -- | -- | -- | -- | -- | 23 | | [fake-org/fake-repo](https://github.com/fake-org/fake-repo) | [846b3dd](https://github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo/badge)](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo) | 2023-02-20 | 5 / [Details](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/compare/39a08ee8b8d3818677eb823cb566f36b1b1c4671/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [View](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [Fix it](https://app.stepsecurity.io/securerepo?repo=fake-org/fake-repo) | 24 | 25 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 26 | " 27 | `; 28 | 29 | exports[`Utils generateReportContent Should render template with scores and title 1`] = ` 30 | "# OpenSSF Scorecard Report 31 | 32 | ## Summary 33 | 34 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 35 | | -- | -- | -- | -- | -- | -- | -- | 36 | | [fake-org/fake-repo](https://github.com/fake-org/fake-repo) | [846b3dd](https://github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | 10 | 2023-02-20 | 5 / [Details](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/compare/39a08ee8b8d3818677eb823cb566f36b1b1c4671/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [View](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [Fix it](https://app.stepsecurity.io/securerepo?repo=fake-org/fake-repo) | 37 | 38 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 39 | " 40 | `; 41 | 42 | exports[`Utils generateReportContent Should render template with scores only 1`] = ` 43 | "# OpenSSF Scorecard Report 44 | 45 | ## Summary 46 | 47 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 48 | | -- | -- | -- | -- | -- | -- | -- | 49 | | [fake-org/fake-repo](https://github.com/fake-org/fake-repo) | [846b3dd](https://github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo/badge)](https://api.securityscorecards.dev/projects/github.com/fake-org/fake-repo) | 2023-02-20 | 5 / [Details](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/compare/39a08ee8b8d3818677eb823cb566f36b1b1c4671/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [View](https://ossf.github.io/scorecard-visualizer/#/projects/github.com/fake-org/fake-repo/commit/846b3ddb5f75d95235e94d9eb52e920f4a067338) | [Fix it](https://app.stepsecurity.io/securerepo?repo=fake-org/fake-repo) | 50 | 51 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 52 | " 53 | `; 54 | 55 | exports[`Utils generateReportContent Should render template with title only 1`] = ` 56 | "# OpenSSF Scorecard Report 57 | 58 | ## Summary 59 | 60 | 61 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 62 | " 63 | `; 64 | -------------------------------------------------------------------------------- /__tests__/utils.test.js: -------------------------------------------------------------------------------- 1 | const { validateDatabaseIntegrity, validateScopeIntegrity, generateReportContent } = require('../src/utils') 2 | const { database, scope, scores } = require('../__fixtures__') 3 | 4 | describe('Utils', () => { 5 | describe('validateDatabaseIntegrity', () => { 6 | it('Should manage a full database', () => { 7 | expect(() => validateDatabaseIntegrity(database.fullDatabase)).not.toThrow() 8 | }) 9 | it('Should manage an empty database', () => { 10 | expect(() => validateDatabaseIntegrity(database.emptyDatabase)).not.toThrow() 11 | }) 12 | }) 13 | 14 | describe('validateScopeIntegrity', () => { 15 | it('Should manage a big Scope', () => { 16 | expect(() => validateScopeIntegrity(scope.fullScope)).not.toThrow() 17 | }) 18 | it('Should manage a empty Scope', () => { 19 | expect(() => validateScopeIntegrity(scope.emptyScope)).not.toThrow() 20 | }) 21 | }) 22 | 23 | describe('generateReportContent', () => { 24 | it('Should render template with scores and title', async () => { 25 | const reportTagsEnabled = false 26 | const reportTool = 'scorecard-visualizer' 27 | const report = await generateReportContent({ scores, renderBadge: reportTagsEnabled, reportTool }) 28 | expect(report).toMatchSnapshot() 29 | }) 30 | it('Should render template with scores only', async () => { 31 | const reportTagsEnabled = true 32 | const reportTool = 'scorecard-visualizer' 33 | const report = await generateReportContent({ scores, renderBadge: reportTagsEnabled, reportTool }) 34 | expect(report).toMatchSnapshot() 35 | }) 36 | it('Should render template with title only', async () => { 37 | const emptyScores = [] 38 | const reportTagsEnabled = false 39 | const reportTool = 'scorecard-visualizer' 40 | const report = await generateReportContent({ scores: emptyScores, renderBadge: reportTagsEnabled, reportTool }) 41 | expect(report).toMatchSnapshot() 42 | }) 43 | it('Should render template with deps.dev as renderTool', async () => { 44 | const reportTagsEnabled = true 45 | const reportTool = 'deps.dev' 46 | const report = await generateReportContent({ scores, renderBadge: reportTagsEnabled, reportTool }) 47 | expect(report).toMatchSnapshot() 48 | }) 49 | it('Should render template with scorecard-visualizer as renderTool', async () => { 50 | const reportTagsEnabled = true 51 | const reportTool = 'scorecard-visualizer' 52 | const report = await generateReportContent({ scores, renderBadge: reportTagsEnabled, reportTool }) 53 | expect(report).toMatchSnapshot() 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'OpenSSF Scorecard Monitor' 2 | description: 'Monitor OpenSSF Scorecard evolution over time' 3 | author: 'OpenSSF Scorecard Authors' 4 | 5 | inputs: 6 | scope: 7 | description: 'File that includes the list of repositories to monitor' 8 | required: true 9 | database: 10 | description: 'File that stores the state of the scorecard' 11 | required: true 12 | report: 13 | description: 'File that stores the report of the scorecard' 14 | required: true 15 | auto-commit: 16 | description: 'Automatically commit the changes to the repository' 17 | required: false 18 | auto-push: 19 | description: 'Automatically push the changes to the repository' 20 | required: false 21 | generate-issue: 22 | description: 'Automatically generate an issue with the discrepancies' 23 | required: false 24 | issue-title: 25 | description: 'Title of the issue to be generated' 26 | required: false 27 | default: "OpenSSF Scorecard Report Updated!" 28 | issue-assignees: 29 | description: 'List of assignees for the issue to be generated' 30 | required: false 31 | issue-labels: 32 | description: 'List of labels for the issue to be generated' 33 | required: false 34 | discovery-enabled: 35 | description: 'Enable the automatic update of the scope file' 36 | required: false 37 | discovery-orgs: 38 | description: 'List of organizations to be included in the scope file' 39 | required: false 40 | report-tags-enabled: 41 | description: 'Enable the use of tags in the report' 42 | required: false 43 | report-start-tag: 44 | description: 'Start tag to be used in the report' 45 | required: false 46 | default: "" 47 | report-end-tag: 48 | description: 'End tag to be used in the report' 49 | required: false 50 | default: "" 51 | github-token: 52 | description: 'Token to access the repository' 53 | required: true 54 | max-request-in-parallel: 55 | description: 'Maximum number of HTTP requests to be executed in parallel' 56 | required: false 57 | default: "10" 58 | render-badge: 59 | description: 'Render the OpenSSF Scorecard badge in the report' 60 | required: false 61 | default: "false" 62 | report-tool: 63 | description: 'Tool to be included as link in the report' 64 | required: false 65 | default: "scorecard-visualizer" 66 | 67 | outputs: 68 | scores: 69 | description: 'Score data in JSON format' 70 | 71 | runs: 72 | using: 'node20' 73 | main: 'dist/index.js' 74 | 75 | # https://actions-cool.github.io/github-action-branding/ 76 | branding: 77 | icon: 'clipboard' 78 | color: 'red' -------------------------------------------------------------------------------- /dist/issue.ejs: -------------------------------------------------------------------------------- 1 | Hello! 2 | 3 | There are changes in your OpenSSF Scorecard report. 4 | 5 | Please review the following changes and take action if necessary. 6 | 7 | ## Summary 8 | 9 | There are changes in the following repositories: 10 | 11 | <%_ if (scores.length) { -%> 12 | | Repository | Commit | Score | Score Delta | Report | StepSecurity | 13 | | -- | -- | -- | -- | -- | -- | 14 | <%_ } -%> 15 | <%_ scores.forEach( score => { -%> 16 | | [<%= score.org %>/<%= score.repo %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>) | [<%= score.commit.slice(0, 7) %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>/commit/<%= score.commit %>) | <% if (!renderBadge) { -%><%= score.score %> <%_ } -%> <%_ if (renderBadge) { -%> [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>/badge)](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>) <%_ } -%> | <%= score.currentDiff || 0 %><%_ if (score.prevCommit) { -%> / [Details](<%= getReportUrl(score.org, score.repo, score.commit, score.prevCommit) %>)<%_ } -%> | [View](<%= getReportUrl(score.org, score.repo, score.commit) %>) | [Fix it](https://app.stepsecurity.io/securerepo?repo=<%= score.org %>/<%= score.repo %>) | 17 | <%_ }); -%> 18 | 19 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 20 | -------------------------------------------------------------------------------- /dist/report.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (!reportTagsEnabled) { -%> 2 | # OpenSSF Scorecard Report 3 | 4 | ## Summary 5 | <%_ } -%> 6 | 7 | <%_ if (scores.length) { -%> 8 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 9 | | -- | -- | -- | -- | -- | -- | -- | 10 | <%_ } -%> 11 | <%_ scores.forEach( score => { -%> 12 | | [<%= score.org %>/<%= score.repo %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>) | [<%= score.commit.slice(0, 7) %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>/commit/<%= score.commit %>) | <% if (!renderBadge) { -%><%= score.score %> <%_ } -%><% if (renderBadge) { -%> [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>/badge)](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>) <%_ } -%> | <%= score.date %> | <%= score.currentDiff || 0 %> <%_ if (score.prevCommit) { -%> / [Details](<%= getReportUrl(score.org, score.repo, score.commit, score.prevCommit) %>)<%_ } -%> | [View](<%= getReportUrl(score.org, score.repo, score.commit) %>) | [Fix it](https://app.stepsecurity.io/securerepo?repo=<%= score.org %>/<%= score.repo %>) | 13 | <%_ }); -%> 14 | 15 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config') 2 | 3 | module.exports = { 4 | testPathIgnorePatterns: [ 5 | ...defaults.testPathIgnorePatterns 6 | ], 7 | testEnvironment: 'node', 8 | collectCoverageFrom: [ 9 | 'src/**/*.js' 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openssf-scorecard-monitor", 3 | "version": "2.0.0-beta8", 4 | "description": "A simple way to monitor OpenSSF Scorecard at organization level", 5 | "main": "src/index.js", 6 | "private": true, 7 | "scripts": { 8 | "build": "ncc build src/action.js -o dist", 9 | "test": "FORCE_COLOR=3 jest --verbose", 10 | "test:update": "FORCE_COLOR=3 jest --verbose --u", 11 | "test:coverage": "FORCE_COLOR=3 jest --verbose --coverage", 12 | "test:watch": "FORCE_COLOR=3 jest --verbose --watchAll", 13 | "lint": "standard", 14 | "lint:fix": "standard --fix", 15 | "release": "standard-version", 16 | "release:minor": "standard-version --release-as minor", 17 | "release:patch": "standard-version --release-as patch", 18 | "release:major": "standard-version --release-as major" 19 | }, 20 | "standard": { 21 | "env": [ 22 | "jest" 23 | ], 24 | "ignore": [ 25 | "dist/**" 26 | ] 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/ossf/scorecard-monitor" 31 | }, 32 | "keywords": [ 33 | "openssf-scorecard", 34 | "maintainers" 35 | ], 36 | "author": "Ulises Gascon", 37 | "bugs": { 38 | "url": "https://github.com/ossf/scorecard-monitor" 39 | }, 40 | "homepage": "https://github.com/ossf/scorecard-monitor", 41 | "dependencies": { 42 | "@actions/core": "1.10.1", 43 | "@actions/exec": "1.1.1", 44 | "@actions/github": "6.0.0", 45 | "@ulisesgascon/array-to-chunks": "2.0.0", 46 | "@ulisesgascon/is-different": "2.0.0", 47 | "@ulisesgascon/normalize-boolean": "2.0.0", 48 | "@ulisesgascon/soft-assign-deep-property": "2.0.0", 49 | "@ulisesgascon/text-tags-manager": "2.0.0", 50 | "@vercel/ncc": "0.38.1", 51 | "ajv": "8.12.0", 52 | "ajv-formats": "2.1.1", 53 | "ejs": "3.1.10", 54 | "got": "11.8.6" 55 | }, 56 | "engines": { 57 | "node": ">=20.0.0" 58 | }, 59 | "devDependencies": { 60 | "@commitlint/cli": "18.4.4", 61 | "@commitlint/config-conventional": "18.4.4", 62 | "husky": "8.0.3", 63 | "jest": "29.7.0", 64 | "jest-config": "29.7.0", 65 | "standard": "17.1.0", 66 | "standard-version": "9.5.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /schemas/database.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "github.com": { 5 | "type": "object", 6 | "patternProperties": { 7 | "^[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_.]+$": { 8 | "type": "object", 9 | "patternProperties": { 10 | "^[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_.]+$": { 11 | "type": "object", 12 | "properties": { 13 | "previous": { 14 | "type": "array", 15 | "items": { 16 | "type": "object", 17 | "properties": { 18 | "score": { 19 | "type": "number", 20 | "minimum": 0, 21 | "maximum": 10 22 | }, 23 | "date": { 24 | "type": "string", 25 | "format": "date-time" 26 | }, 27 | "commit": { 28 | "type": "string", 29 | "pattern": "^[a-f0-9]{40}$" 30 | } 31 | }, 32 | "additionalProperties": false, 33 | "required": [ 34 | "score", 35 | "date", 36 | "commit" 37 | ] 38 | }, 39 | "minItems": 0, 40 | "default": [] 41 | }, 42 | "current": { 43 | "type": "object", 44 | "properties": { 45 | "score": { 46 | "type": "number", 47 | "minimum": 0, 48 | "maximum": 10 49 | }, 50 | "date": { 51 | "type": "string", 52 | "format": "date-time" 53 | }, 54 | "commit": { 55 | "type": "string", 56 | "pattern": "^[a-f0-9]{40}$" 57 | } 58 | }, 59 | "additionalProperties": false, 60 | "required": [ 61 | "score", 62 | "date", 63 | "commit" 64 | ] 65 | } 66 | }, 67 | "additionalProperties": false, 68 | "required": [ 69 | "previous", 70 | "current" 71 | ] 72 | } 73 | } 74 | } 75 | } 76 | } 77 | }, 78 | "additionalProperties": false, 79 | "required": [ 80 | "github.com" 81 | ] 82 | } -------------------------------------------------------------------------------- /schemas/scope.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "github.com": { 5 | "type": "object", 6 | "patternProperties": { 7 | "^[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_.]+$": { 8 | "type": "object", 9 | "properties": { 10 | "included": { 11 | "type": "array", 12 | "items": { 13 | "type": "string" 14 | }, 15 | "minItems": 0, 16 | "default": [] 17 | }, 18 | "excluded": { 19 | "type": "array", 20 | "items": { 21 | "type": "string" 22 | }, 23 | "minItems": 0, 24 | "default": [] 25 | } 26 | }, 27 | "additionalProperties": false, 28 | "required": [ 29 | "included", 30 | "excluded" 31 | ] 32 | } 33 | } 34 | } 35 | }, 36 | "additionalProperties": false, 37 | "required": [ 38 | "github.com" 39 | ] 40 | } -------------------------------------------------------------------------------- /src/action.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core') 2 | const github = require('@actions/github') 3 | const exec = require('@actions/exec') 4 | const { normalizeBoolean } = require('@ulisesgascon/normalize-boolean') 5 | const { existsSync } = require('fs') 6 | const { readFile, writeFile, stat } = require('fs').promises 7 | const { isDifferent } = require('@ulisesgascon/is-different') 8 | const { updateOrCreateSegment } = require('@ulisesgascon/text-tags-manager') 9 | const { generateScores, generateScope } = require('./') 10 | const { validateDatabaseIntegrity, validateScopeIntegrity } = require('./utils') 11 | 12 | async function run () { 13 | let octokit 14 | // Context 15 | const context = github.context 16 | // Inputs 17 | const scopePath = core.getInput('scope', { required: true }) 18 | const databasePath = core.getInput('database', { required: true }) 19 | const reportPath = core.getInput('report', { required: true }) 20 | // Options 21 | const maxRequestInParallel = parseInt(core.getInput('max-request-in-parallel') || 10) 22 | const generateIssue = normalizeBoolean(core.getInput('generate-issue')) 23 | const autoPush = normalizeBoolean(core.getInput('auto-push')) 24 | const autoCommit = normalizeBoolean(core.getInput('auto-commit')) 25 | const issueTitle = core.getInput('issue-title') || 'OpenSSF Scorecard Report Updated!' 26 | const issueAssignees = core.getInput('issue-assignees').split(',').filter(x => x !== '').map(x => x.trim()) || [] 27 | const issueLabels = core.getInput('issue-labels').split(',').filter(x => x !== '').map(x => x.trim()) || [] 28 | const githubToken = core.getInput('github-token') 29 | const discoveryEnabled = normalizeBoolean(core.getInput('discovery-enabled')) 30 | const discoveryOrgs = core.getInput('discovery-orgs').split(',').filter(x => x !== '').map(x => x.trim()) || [] 31 | const reportTagsEnabled = normalizeBoolean(core.getInput('report-tags-enabled')) 32 | const startTag = core.getInput('report-start-tag') || '' 33 | const endTag = core.getInput('report-end-tag') || '' 34 | const renderBadge = normalizeBoolean(core.getInput('render-badge')) 35 | const reportTool = core.getInput('report-tool') || 'scorecard-visualizer' 36 | 37 | const availableReportTools = ['scorecard-visualizer', 'deps.dev'] 38 | if (!availableReportTools.includes(reportTool)) { 39 | throw new Error(`The report-tool is not valid, please use: ${availableReportTools.join(', ')}`) 40 | } 41 | 42 | // Error Handling 43 | if (!githubToken && [autoPush, autoCommit, generateIssue, discoveryEnabled].some(value => value)) { 44 | throw new Error('Github token is required for push, commit, create an issue and discovery operations!') 45 | } 46 | 47 | if (discoveryEnabled && !discoveryOrgs.length) { 48 | throw new Error('Discovery is enabled but no organizations were provided!') 49 | } 50 | 51 | if (githubToken) { 52 | octokit = github.getOctokit(githubToken) 53 | } 54 | 55 | let database = {} 56 | let scope = { 'github.com': {} } 57 | let originalReportContent = '' 58 | 59 | // check if scope exists 60 | core.info('Checking if scope file exists...') 61 | const existScopeFile = existsSync(scopePath) 62 | if (!existScopeFile && !discoveryEnabled) { 63 | throw new Error('Scope file does not exist and discovery is not enabled') 64 | } 65 | 66 | // Use scope file if it exists 67 | if (existScopeFile) { 68 | core.debug('Scope file exists, using it...') 69 | scope = await readFile(scopePath, 'utf8').then(content => JSON.parse(content)) 70 | validateScopeIntegrity(scope) 71 | } 72 | 73 | if (discoveryEnabled) { 74 | core.info(`Starting discovery for the organizations ${discoveryOrgs}...`) 75 | scope = await generateScope({ octokit, orgs: discoveryOrgs, scope, maxRequestInParallel }) 76 | } 77 | 78 | // Check if database exists 79 | core.info('Checking if database exists...') 80 | const existDatabaseFile = existsSync(databasePath) 81 | if (existDatabaseFile) { 82 | database = await readFile(databasePath, 'utf8').then(content => JSON.parse(content)) 83 | validateDatabaseIntegrity(database) 84 | } else { 85 | core.info('Database does not exist, creating new database') 86 | } 87 | 88 | // Check if report exists as the content will be used to update the report with the tags 89 | if (reportTagsEnabled) { 90 | try { 91 | core.info('Checking if report exists...') 92 | await stat(reportPath) 93 | originalReportContent = await readFile(reportPath, 'utf8') 94 | } catch (error) { 95 | core.info('Previous Report does not exist, ignoring previous content for tags...') 96 | } 97 | } 98 | 99 | // PROCESS 100 | core.info('Generating scores...') 101 | const { reportContent, issueContent, database: newDatabaseState } = await generateScores({ scope, database, maxRequestInParallel, reportTagsEnabled, renderBadge, reportTool }) 102 | 103 | core.info('Checking database changes...') 104 | const hasChanges = isDifferent(database, newDatabaseState) 105 | 106 | if (!hasChanges) { 107 | core.info('No changes to database, skipping the rest of the process') 108 | return 109 | } 110 | 111 | // Save changes 112 | core.info('Saving changes to database and report') 113 | await writeFile(databasePath, JSON.stringify(newDatabaseState, null, 2)) 114 | await writeFile(reportPath, reportTagsEnabled 115 | ? updateOrCreateSegment({ 116 | original: originalReportContent, 117 | replacementSegment: reportContent, 118 | startTag, 119 | endTag 120 | }) 121 | : reportContent) 122 | 123 | if (discoveryEnabled) { 124 | core.info('Saving changes to scope...') 125 | await writeFile(scopePath, JSON.stringify(scope, null, 2)) 126 | } 127 | 128 | // Commit changes 129 | // @see: https://github.com/actions/checkout#push-a-commit-using-the-built-in-token 130 | if (autoCommit) { 131 | core.info('Committing changes to database and report') 132 | await exec.exec('git config user.name github-actions') 133 | await exec.exec('git config user.email github-actions@github.com') 134 | await exec.exec(`git add ${databasePath}`) 135 | await exec.exec(`git add ${reportPath}`) 136 | if (discoveryEnabled) { 137 | core.info('Committing changes to scope...') 138 | await exec.exec(`git add ${scopePath}`) 139 | } 140 | await exec.exec('git commit -m "Updated Scorecard Report"') 141 | } 142 | 143 | // Push changes 144 | if (autoPush) { 145 | // @see: https://github.com/actions-js/push/blob/master/start.sh#L43 146 | core.info('Pushing changes to database and report') 147 | const remoteRepo = `https://${process.env.INPUT_GITHUB_ACTOR}:${githubToken}@github.com/${process.env.INPUT_REPOSITORY}.git` 148 | await exec.exec(`git push origin ${process.env.GITHUB_HEAD_REF} --force --no-verify --repo ${remoteRepo}`) 149 | } 150 | 151 | // Issue creation 152 | if (generateIssue && issueContent) { 153 | core.info('Creating issue...') 154 | await octokit.rest.issues.create({ 155 | ...context.repo, 156 | title: issueTitle, 157 | body: issueContent, 158 | labels: issueLabels, 159 | assignees: issueAssignees 160 | }) 161 | } 162 | } 163 | 164 | run() 165 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core') 2 | const { getProjectScore, generateIssueContent, generateReportContent, getScore, saveScore } = require('./utils') 3 | const { chunkArray } = require('@ulisesgascon/array-to-chunks') 4 | 5 | const generateScope = async ({ octokit, orgs, scope, maxRequestInParallel }) => { 6 | const platform = 'github.com' 7 | const newScope = {} 8 | newScope[platform] = { } 9 | const organizationRepos = {} 10 | 11 | // Collect all the repos for each org 12 | for (let index = 0; index < orgs.length; index++) { 13 | const org = orgs[index] 14 | core.debug(`Processing org ${index + 1}/${orgs.length}: ${org}`) 15 | 16 | // Check for Org or User and collect the first 100 repos 17 | let entityType = 'org' 18 | const repoList = [] 19 | try { 20 | const { data: repos } = await octokit.rest.repos.listForOrg({ org, type: 'public', per_page: 100 }) 21 | core.debug(`Got ${repos.length} repos for org: ${org}`) 22 | repoList.push(...repos.map(entity => entity.name)) 23 | } catch (error) { 24 | entityType = 'user' 25 | const { data: repos } = await octokit.rest.repos.listForUser({ username: org, type: 'public', per_page: 100 }) 26 | core.debug(`Got ${repos.length} repos for user: ${org}`) 27 | repoList.push(...repos.map(entity => entity.name)) 28 | } 29 | 30 | // Check if the org or user has more than 100 repos and requires pagination management 31 | if (repoList.length === 100) { 32 | let page = 2 33 | let hasMore = true 34 | const entityInApi = {} 35 | entityInApi[entityType === 'org' ? 'org' : 'username'] = org 36 | while (hasMore) { 37 | core.debug(`Getting page ${page} for ${entityType}: ${org}`) 38 | const { data: repos, headers } = await octokit.rest.repos[entityType === 'org' ? 'listForOrg' : 'listForUser']({ ...entityInApi, type: 'public', per_page: 100, page }) 39 | core.debug(`Got ${repos.length} repos for ${entityType}: ${org}`) 40 | repoList.push(...repos.map(entity => entity.name)) 41 | hasMore = headers.link.includes('rel="next"') 42 | page += 1 43 | } 44 | } 45 | 46 | organizationRepos[org] = repoList 47 | 48 | // Filter the repos that will be part of the scope 49 | let newReposInScope = organizationRepos[org] 50 | 51 | // Check if the org is already in scope and then filter the new repos 52 | if (scope[platform][org]) { 53 | // @TODO: Ensure that the included and excluded are covered by JSON Schemas 54 | newReposInScope = organizationRepos[org].filter(repo => !scope[platform][org].included.includes(repo) && !scope[platform][org].excluded.includes(repo)) 55 | } 56 | 57 | // Try the new repos against the API and filter the ones that have a score (and respect the http request limits) 58 | core.debug(`Total new projects to check against the score API: ${newReposInScope.length}`) 59 | 60 | const chunks = chunkArray(newReposInScope, maxRequestInParallel) 61 | core.debug(`Total chunks: ${chunks.length}`) 62 | 63 | const newReposInScopeWithScore = [] 64 | for (let index = 0; index < chunks.length; index++) { 65 | const chunk = chunks[index] 66 | core.debug(`Processing chunk ${index + 1}/${chunks.length}`) 67 | core.debug(`Current projects: ${chunks}`) 68 | 69 | await Promise.all(chunk.map(async (repo) => { 70 | try { 71 | // The Scorecard API will return 404 if the repo is not available 72 | await getProjectScore({ platform, org, repo }) 73 | core.debug(`Scorecard is eligible for ${platform}/${org}/${repo})`) 74 | newReposInScopeWithScore.push(repo) 75 | } catch (error) { 76 | core.debug(`No Scorecard for ${platform}/${org}/${repo})`) 77 | } 78 | return Promise.resolve() 79 | })) 80 | } 81 | 82 | core.debug(`Total new projects to add to the scope: ${newReposInScopeWithScore.length}`) 83 | 84 | // Add just the new repos to the scope 85 | if (scope[platform][org]) { 86 | newScope[platform][org] = { 87 | included: [...scope[platform][org].included, ...newReposInScopeWithScore], 88 | excluded: [...scope[platform][org].excluded] 89 | } 90 | } else { 91 | newScope[platform][org] = { 92 | included: newReposInScopeWithScore, 93 | excluded: [] 94 | } 95 | } 96 | } 97 | 98 | return newScope 99 | } 100 | 101 | const generateScores = async ({ scope, database: currentDatabase, maxRequestInParallel, reportTagsEnabled, renderBadge, reportTool }) => { 102 | // @TODO: Improve deep clone logic 103 | const database = JSON.parse(JSON.stringify(currentDatabase)) 104 | const platform = 'github.com' 105 | 106 | // @TODO: End the action if there are no projects in scope? 107 | 108 | const orgs = Object.keys(scope[platform]) 109 | core.debug(`Total Orgs/Users in scope: ${orgs.length}`) 110 | 111 | // Structure Projects 112 | const projects = [] 113 | 114 | orgs.forEach((org) => { 115 | const repos = scope[platform][org].included 116 | repos.forEach((repo) => projects.push({ org, repo })) 117 | }) 118 | 119 | core.debug(`Total Projects in scope: ${projects.length}`) 120 | 121 | const chunks = chunkArray(projects, maxRequestInParallel) 122 | core.debug(`Total chunks: ${chunks.length}`) 123 | 124 | const scores = [] 125 | 126 | for (let index = 0; index < chunks.length; index++) { 127 | const chunk = chunks[index] 128 | core.debug(`Processing chunk ${index + 1}/${chunks.length}`) 129 | 130 | const chunkScores = await Promise.all(chunk.map(async ({ org, repo }) => { 131 | const { score, date, commit } = await getProjectScore({ platform, org, repo }) 132 | core.debug(`Got project score for ${platform}/${org}/${repo}: ${score} (${date})`) 133 | 134 | const storedScore = getScore({ database, platform, org, repo }) 135 | 136 | const scoreData = { platform, org, repo, score, date, commit } 137 | // If no stored score then record if score is different then: 138 | if (!storedScore || storedScore.score !== score) { 139 | saveScore({ database, platform, org, repo, score, date, commit }) 140 | } 141 | 142 | // Add previous score and date if available to the report 143 | if (storedScore) { 144 | scoreData.prevScore = storedScore.score 145 | scoreData.prevDate = storedScore.date 146 | scoreData.prevCommit = storedScore.commit 147 | 148 | if (storedScore.score !== score) { 149 | scoreData.currentDiff = parseFloat((score - storedScore.score).toFixed(1)) 150 | } 151 | } 152 | 153 | return scoreData 154 | })) 155 | 156 | scores.push(...chunkScores) 157 | } 158 | 159 | core.debug('All the scores are already collected') 160 | 161 | const reportContent = await generateReportContent({ scores, reportTagsEnabled, renderBadge, reportTool }) 162 | const issueContent = await generateIssueContent({ scores, renderBadge, reportTool }) 163 | 164 | // SET OUTPUTS 165 | core.setOutput('scores', scores) 166 | 167 | return { reportContent, issueContent, database } 168 | } 169 | 170 | module.exports = { 171 | generateScope, 172 | generateScores 173 | } 174 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const got = require('got') 2 | const core = require('@actions/core') 3 | const ejs = require('ejs') 4 | const { readFile } = require('fs').promises 5 | const { join } = require('path') 6 | const { softAssign } = require('@ulisesgascon/soft-assign-deep-property') 7 | const databaseSchema = require('../schemas/database.json') 8 | const scopeSchema = require('../schemas/scope.json') 9 | 10 | const Ajv = require('ajv') 11 | const addFormats = require('ajv-formats').default 12 | const ajv = new Ajv() 13 | addFormats(ajv) 14 | 15 | const validateAgainstSchema = (schema, name) => (data) => { 16 | const valid = ajv.validate(schema, data) 17 | if (!valid) { 18 | throw new Error(`Check: ${name} file as the file is corrupted. Invalid data: ${ajv.errorsText()}`) 19 | } 20 | } 21 | 22 | const getProjectScore = async ({ platform, org, repo }) => { 23 | core.debug(`Getting project score for ${platform}/${org}/${repo}`) 24 | const response = await got(`https://api.securityscorecards.dev/projects/${platform}/${org}/${repo}`) 25 | const { score, date, repo: { commit } = {} } = JSON.parse(response.body) 26 | core.debug(`Got project score for ${platform}/${org}/${repo}: ${score} (${date})`) 27 | return { platform, org, repo, score, date, commit } 28 | } 29 | 30 | const getScore = ({ database, platform, org, repo }) => { 31 | const { current } = database?.[platform]?.[org]?.[repo] || {} 32 | return current || null 33 | } 34 | 35 | const saveScore = ({ database, platform, org, repo, score, date, commit }) => { 36 | softAssign(database, [platform, org, repo, 'previous'], []) 37 | const repoRef = database[platform][org][repo] 38 | 39 | if (repoRef.current) { 40 | repoRef.previous.push(repoRef.current) 41 | } 42 | repoRef.current = { score, date, commit } 43 | } 44 | 45 | const generateReportUrl = reportTool => (org, repo, commit, prevCommit) => { 46 | if (reportTool === 'scorecard-visualizer' && !prevCommit) { 47 | return `https://ossf.github.io/scorecard-visualizer/#/projects/github.com/${org}/${repo}/commit/${commit}` 48 | } 49 | if (reportTool === 'scorecard-visualizer' && prevCommit) { 50 | return `https://ossf.github.io/scorecard-visualizer/#/projects/github.com/${org}/${repo}/compare/${prevCommit}/${commit}` 51 | } 52 | return `https://deps.dev/project/github/${org.toLowerCase()}%2F${repo.toLowerCase()}` 53 | } 54 | 55 | const generateReportContent = async ({ scores, reportTagsEnabled, renderBadge, reportTool }) => { 56 | core.debug('Generating report content') 57 | const template = await readFile(join(process.cwd(), 'templates/report.ejs'), 'utf8') 58 | const getReportUrl = generateReportUrl(reportTool) 59 | return ejs.render(template, { scores, reportTagsEnabled, renderBadge, getReportUrl }) 60 | } 61 | 62 | const generateIssueContent = async ({ scores, renderBadge, reportTool }) => { 63 | core.debug('Generating issue content') 64 | const scoresInScope = scores.filter(({ currentDiff }) => currentDiff) 65 | if (!scoresInScope.length) { 66 | return null 67 | } 68 | const template = await readFile(join(process.cwd(), 'templates/issue.ejs'), 'utf8') 69 | const getReportUrl = generateReportUrl(reportTool) 70 | return ejs.render(template, { scores: scoresInScope, renderBadge, getReportUrl }) 71 | } 72 | 73 | module.exports = { 74 | validateDatabaseIntegrity: validateAgainstSchema(databaseSchema, 'database'), 75 | validateScopeIntegrity: validateAgainstSchema(scopeSchema, 'scope'), 76 | getProjectScore, 77 | saveScore, 78 | getScore, 79 | generateReportContent, 80 | generateIssueContent 81 | } 82 | -------------------------------------------------------------------------------- /templates/issue.ejs: -------------------------------------------------------------------------------- 1 | Hello! 2 | 3 | There are changes in your OpenSSF Scorecard report. 4 | 5 | Please review the following changes and take action if necessary. 6 | 7 | ## Summary 8 | 9 | There are changes in the following repositories: 10 | 11 | <%_ if (scores.length) { -%> 12 | | Repository | Commit | Score | Score Delta | Report | StepSecurity | 13 | | -- | -- | -- | -- | -- | -- | 14 | <%_ } -%> 15 | <%_ scores.forEach( score => { -%> 16 | | [<%= score.org %>/<%= score.repo %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>) | [<%= score.commit.slice(0, 7) %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>/commit/<%= score.commit %>) | <% if (!renderBadge) { -%><%= score.score %> <%_ } -%> <%_ if (renderBadge) { -%> [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>/badge)](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>) <%_ } -%> | <%= score.currentDiff || 0 %><%_ if (score.prevCommit) { -%> / [Details](<%= getReportUrl(score.org, score.repo, score.commit, score.prevCommit) %>)<%_ } -%> | [View](<%= getReportUrl(score.org, score.repo, score.commit) %>) | [Fix it](https://app.stepsecurity.io/securerepo?repo=<%= score.org %>/<%= score.repo %>) | 17 | <%_ }); -%> 18 | 19 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 20 | -------------------------------------------------------------------------------- /templates/report.ejs: -------------------------------------------------------------------------------- 1 | <%_ if (!reportTagsEnabled) { -%> 2 | # OpenSSF Scorecard Report 3 | 4 | ## Summary 5 | <%_ } -%> 6 | 7 | <%_ if (scores.length) { -%> 8 | | Repository | Commit | Score | Date | Score Delta | Report | StepSecurity | 9 | | -- | -- | -- | -- | -- | -- | -- | 10 | <%_ } -%> 11 | <%_ scores.forEach( score => { -%> 12 | | [<%= score.org %>/<%= score.repo %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>) | [<%= score.commit.slice(0, 7) %>](https://<%= score.platform %>/<%= score.org %>/<%= score.repo %>/commit/<%= score.commit %>) | <% if (!renderBadge) { -%><%= score.score %> <%_ } -%><% if (renderBadge) { -%> [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>/badge)](https://api.securityscorecards.dev/projects/github.com/<%= score.org %>/<%= score.repo %>) <%_ } -%> | <%= score.date %> | <%= score.currentDiff || 0 %> <%_ if (score.prevCommit) { -%> / [Details](<%= getReportUrl(score.org, score.repo, score.commit, score.prevCommit) %>)<%_ } -%> | [View](<%= getReportUrl(score.org, score.repo, score.commit) %>) | [Fix it](https://app.stepsecurity.io/securerepo?repo=<%= score.org %>/<%= score.repo %>) | 13 | <%_ }); -%> 14 | 15 | _Report generated by [OpenSSF Scorecard Monitor](https://github.com/ossf/scorecard-monitor)._ 16 | --------------------------------------------------------------------------------