Compare commits

...

33 Commits
v2.0.0 ... main

Author SHA1 Message Date
Daz DeBoer
f9c9c575b8
Bump to v3.5.0 2024-07-15 13:31:04 -06:00
Inaki Villar
88425854a3
Bump to v3.4.2 2024-06-17 11:46:29 -07:00
Daz DeBoer
9ba54b687b
Bump to v3.4.1 2024-06-14 21:35:04 -06:00
Daz DeBoer
26ffd686ec
Bump to v3.4.0 2024-06-13 15:12:19 -06:00
Daz DeBoer
216d1ad2b3
Bump to v3.3.2 2024-04-25 14:50:56 -06:00
daz
5188e9b552
Bump to use v3.3.1 2024-04-18 13:54:36 -06:00
Daz DeBoer
460a3ca55f
Delegate to 'gradle/actions/wrapper-validation' (#200)
Now that 'gradle/actions/wrapper-validation' has been released as v3.3.0, 
we remove this implementation and delegate via a composite action.

Fixes #198
2024-04-12 15:03:36 -06:00
daz
b5418f5a58
Build dist 2024-04-09 16:18:42 -06:00
dependabot[bot]
6613b81c19
Bump the npm-dependencies group with 2 updates
Bumps the npm-dependencies group with 2 updates: [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [typescript](https://github.com/Microsoft/TypeScript).


Updates `@typescript-eslint/parser` from 7.2.0 to 7.4.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.4.0/packages/parser)

Updates `typescript` from 5.4.2 to 5.4.3
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.2...v5.4.3)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 16:18:18 -06:00
dependabot[bot]
35bb224882
Bump undici from 5.28.3 to 5.28.4
Bumps [undici](https://github.com/nodejs/undici) from 5.28.3 to 5.28.4.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.3...v5.28.4)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 16:18:17 -06:00
bigdaz
ea90fd0c75 Update known wrapper checksums 2024-04-09 16:16:13 -06:00
daz
b231772637 Build outputs 2024-03-21 21:21:28 -06:00
bigdaz
ad66d65d25 Update known wrapper checksums 2024-03-21 21:21:28 -06:00
dependabot[bot]
34f4d9f0dc Bump undici from 5.28.2 to 5.28.3
Bumps [undici](https://github.com/nodejs/undici) from 5.28.2 to 5.28.3.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.2...v5.28.3)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-21 21:21:28 -06:00
dependabot[bot]
4a956f6d02 Bump the npm-dependencies group with 6 updates
Bumps the npm-dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `6.21.0` | `7.2.0` |
| [eslint](https://github.com/eslint/eslint) | `8.56.0` | `8.57.0` |
| [eslint-plugin-github](https://github.com/github/eslint-plugin-github) | `4.10.1` | `4.10.2` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `27.6.3` | `27.9.0` |
| [nock](https://github.com/nock/nock) | `13.5.1` | `13.5.4` |
| [typescript](https://github.com/Microsoft/TypeScript) | `5.3.3` | `5.4.2` |


Updates `@typescript-eslint/parser` from 6.21.0 to 7.2.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.2.0/packages/parser)

Updates `eslint` from 8.56.0 to 8.57.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0)

Updates `eslint-plugin-github` from 4.10.1 to 4.10.2
- [Release notes](https://github.com/github/eslint-plugin-github/releases)
- [Commits](https://github.com/github/eslint-plugin-github/compare/v4.10.1...v4.10.2)

Updates `eslint-plugin-jest` from 27.6.3 to 27.9.0
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v27.6.3...v27.9.0)

Updates `nock` from 13.5.1 to 13.5.4
- [Release notes](https://github.com/nock/nock/releases)
- [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nock/nock/compare/v13.5.1...v13.5.4)

Updates `typescript` from 5.3.3 to 5.4.2
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.3.3...v5.4.2)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: eslint-plugin-github
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: eslint-plugin-jest
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: nock
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-21 21:21:28 -06:00
dependabot[bot]
63d15e7a1e Bump the npm-dependencies group with 1 update
Bumps the npm-dependencies group with 1 update: [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser).


Updates `@typescript-eslint/parser` from 6.20.0 to 6.21.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.21.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-10 10:04:29 -07:00
daz
699bb18358
Build outputs 2024-02-10 09:54:37 -07:00
bigdaz
69d55fad27
Update known wrapper checksums 2024-02-10 09:53:24 -07:00
dependabot[bot]
e636552d42
Bump the npm-dependencies group with 1 update
Bumps the npm-dependencies group with 1 update: [prettier](https://github.com/prettier/prettier).


Updates `prettier` from 3.2.4 to 3.2.5
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.4...3.2.5)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-10 09:52:57 -07:00
daz
85cde3f5a1
Add clarification about running in repository root 2024-02-07 08:26:06 -07:00
daz
8a2cbc9ccf
Clarify that wrapper-validation should precede setup-gradle 2024-02-07 08:22:46 -07:00
daz
d355de3893
Build outputs 2024-02-07 08:13:19 -07:00
Marcono1234
ac96f186ff Hardcoded checksums follow-up 2024-02-07 16:09:51 +01:00
Marcono1234
ca85ed0b62 Extend integration tests 2024-02-07 16:08:28 +01:00
bigdaz
c4ee1cd97a Update known wrapper checksums 2024-02-07 04:57:13 +01:00
Róbert Papp
a3555efcbf
Clarify output format (#177) 2024-02-06 20:56:02 -07:00
daz
24d65a10ac
Merge branch 'releases/v2'
* releases/v2:
  Check dist on release branches
  Log multiple errors
2024-02-06 08:25:02 -07:00
daz
a494d935f4
Check dist on release branches 2024-02-06 08:24:05 -07:00
Łukasz Jernaś
21bea8c867
Log multiple errors
# What
Log AggregateError type, when multiple errors are returned
from HTTP client - fixes #174

# Why
We would silently fail otherwise as error.message was empty
for the AggregatedError exception.
2024-02-06 08:18:58 -07:00
daz
83cf5fdbbf
Build outputs 2024-02-01 10:27:22 -07:00
Marcono1234
bb2ea9bd17
Hardcode known wrapper checksums to avoid network requests 2024-02-01 10:26:28 -07:00
Daz DeBoer
2572bdd97b
Fix check-dist workflow (#172)
- Updated package.json to be more consistent with `gradle/actions` repo
- Updated 'check-dist' workflow to be more consistent with `gradle/actions` repo
- Check-dist now actually checks! Fixes #171
2024-01-31 14:19:32 -07:00
Daz DeBoer
1ff5a18cc3
Update README.md to reference v2 2024-01-29 13:59:31 -07:00
29 changed files with 87 additions and 37671 deletions

View File

@ -1,3 +0,0 @@
dist/
lib/
node_modules/

View File

@ -1,54 +0,0 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"i18n-text/no-en": "off",
"no-unused-vars": "off",
"sort-imports": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"camelcase": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-inferrable-types": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true,
"jest/globals": true
}
}

View File

@ -8,14 +8,3 @@ updates:
github-actions:
patterns:
- "*"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "@types/node"
groups:
npm-dependencies:
patterns:
- "*"

View File

@ -1,55 +0,0 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist directory
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: npm
- name: Validate package-lock
run: npx lockfile-lint --path package-lock.json --allowed-hosts npm yarn --validate-https
- name: Install dependencies
run: npm ci
- name: Rebuild the dist/ directory
run: npm run build
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/

View File

@ -7,25 +7,61 @@ on: # rebuild any PRs and main branch changes
- 'releases/*'
jobs:
build: # make sure build/ci work properly
# Integration test for successful validation of wrappers
test-validation-success:
name: 'Test: Validation success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: npm
- run: |
npm -v
node -v
npm install
npm run all
test: # make sure the action works on a clean machine without building
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./
- name: Run wrapper-validation-action
id: action-test
uses: ./
with:
# to allow the invalid wrapper jar present in test data
allow-checksums: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
- name: Check outcome
env:
# Evaluate workflow expressions here as env variable values instead of inside shell script
# below to not accidentally inject code into shell script or break its syntax
FAILED_WRAPPERS: ${{ steps.action-test.outputs.failed-wrapper }}
FAILED_WRAPPERS_MATCHES: ${{ steps.action-test.outputs.failed-wrapper == '' }}
run: |
if [ "$FAILED_WRAPPERS_MATCHES" != "true" ] ; then
echo "'outputs.failed-wrapper' has unexpected content: $FAILED_WRAPPERS"
exit 1
fi
# Integration test for failing validation of wrappers
test-validation-error:
name: 'Test: Validation error'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run wrapper-validation-action
id: action-test
uses: ./
# Expected to fail; validated below
continue-on-error: true
- name: Check outcome
env:
# Evaluate workflow expressions here as env variable values instead of inside shell script
# below to not accidentally inject code into shell script or break its syntax
VALIDATION_FAILED: ${{ steps.action-test.outcome == 'failure' }}
FAILED_WRAPPERS: ${{ steps.action-test.outputs.failed-wrapper }}
FAILED_WRAPPERS_MATCHES: ${{ steps.action-test.outputs.failed-wrapper == '__tests__/data/invalid/gradle-wrapper.jar|__tests__/data/invalid/gradlе-wrapper.jar' }}
run: |
if [ "$VALIDATION_FAILED" != "true" ] ; then
echo "Expected validation to fail, but it didn't"
exit 1
fi
if [ "$FAILED_WRAPPERS_MATCHES" != "true" ] ; then
echo "'outputs.failed-wrapper' has unexpected content: $FAILED_WRAPPERS"
exit 1
fi

View File

@ -1,56 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '24 4 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

102
.gitignore vendored
View File

@ -1,102 +0,0 @@
# Dependency directory
node_modules
# Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
__tests__/runner/*
lib/**/*
.idea/
*.iml

View File

@ -1,3 +0,0 @@
dist/
lib/
node_modules/

View File

@ -1,12 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript",
"endOfLine": "auto"
}

View File

@ -1,2 +0,0 @@
# Configuration file for asdf version manager
nodejs 20.10.0

View File

@ -1,12 +0,0 @@
## Project Goals
We aim to keep the scope of this project limited so that it is easy for maintainers to apply and forget about.
### Goals
To verify that all the gradle-wrapper.jar(s) in a given GitHub repository or pull request against that repo is an official Gradle Wrapper release.
### Non-Goals
It is not the goal of this action to verify that the gradle-wrapper.jar matches a specific version of Gradle,
nor that the version declared in the build.gradle or gradle-wrapper.properties file matches.

109
README.md
View File

@ -1,59 +1,27 @@
<p align="center">
<a href="https://github.com/gradle/wrapper-validation-action/actions"><img alt="gradle/wrapper-validation-action status" src="https://github.com/gradle/wrapper-validation-action/workflows/ci/badge.svg"></a>
</p>
> [!IMPORTANT]
> As of `v3` this action has been superceded by `gradle/actions/wrapper-validation`.
> Any workflow that uses `gradle/wrapper-validation-action@v3` will transparently delegate to `gradle/actions/wrapper-validation@v3`.
>
> Users are encouraged to update their workflows, replacing:
> ```
> uses: gradle/wrapper-validation-action@v3
> ```
>
> with
> ```
> uses: gradle/actions/wrapper-validation@v3
> ```
>
> See the [wrapper-validation documentation](https://github.com/gradle/actions/tree/main/wrapper-validation) for up-to-date documentation for `gradle/actions/wrapper-validation`.
# Gradle Wrapper Validation Action
This action validates the checksums of [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) JAR files present in the source tree and fails if unknown Gradle Wrapper JAR files are found.
This action validates the checksums of _all_ [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) JAR files present in the repository and fails if any unknown Gradle Wrapper JAR files are found.
## The Gradle Wrapper Problem in Open Source
The action should be run in the root of the repository, as it will recursively search for any files named `gradle-wrapper.jar`.
The `gradle-wrapper.jar` is a binary blob of executable code that is checked into nearly
[2.8 Million GitHub Repositories](https://github.com/search?l=&q=filename%3Agradle-wrapper.jar&type=Code).
### Example workflow
Searching across GitHub you can find many pull requests (PRs) with helpful titles like 'Update to Gradle xxx'.
Many of these PRs are contributed by individuals outside of the organization maintaining the project.
Many maintainers are incredibly grateful for these kinds of contributions as it takes an item off of their backlog.
We assume that most maintainers do not consider the security implications of accepting the Gradle Wrapper binary from external contributors.
There is a certain amount of blind trust open source maintainers have.
Further compounding the issue is that maintainers are most often greeted in these PRs with a diff to the `gradle-wrapper.jar` that looks like this.
![Image of a GitHub Diff of Gradle Wrapper displaying text 'Binary file not shown.'](https://user-images.githubusercontent.com/1323708/71915219-477d7780-3149-11ea-9254-90c80dbffb0a.png)
A fairly simple social engineering supply chain attack against open source would be contribute a helpful “Updated to Gradle xxx” PR that contains malicious code hidden inside this binary JAR.
A malicious `gradle-wrapper.jar` could execute, download, or install arbitrary code while otherwise behaving like a completely normal `gradle-wrapper.jar`.
## Solution
We have created a simple GitHub Action that can be applied to any GitHub repository.
This GitHub Action will do one simple task:
verify that any and all `gradle-wrapper.jar` files in the repository match the SHA-256 checksums of any of our official releases.
If any are found that do not match the SHA-256 checksums of our official releases, the action will fail.
Additionally, the action will find and SHA-256 hash all
[homoglyph](https://en.wikipedia.org/wiki/Homoglyph)
variants of files named `gradle-wrapper.jar`,
for example a file named `gradlе-wrapper.jar` (which uses a Cyrillic `е` instead of `e`).
The goal is to prevent homoglyph attacks which may be very difficult to spot in a GitHub diff.
We created an example [Homoglyph attack PR here](https://github.com/JLLeitschuh/playframework/pull/1/files).
## Usage
### Add to an existing Workflow
Simply add this action to your workflow **after** having checked out your source tree and **before** running any Gradle build:
```yaml
uses: gradle/wrapper-validation-action@v1
```
### Add a new dedicated Workflow
Here's a sample complete workflow you can add to your repositories:
**`.github/workflows/gradle-wrapper-validation.yml`**
```yaml
name: "Validate Gradle Wrapper"
on: [push, pull_request]
@ -64,43 +32,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/wrapper-validation-action@v3
```
## Contributing to an external GitHub Repository
As of `v3`, the `gradle/wrapper-validation-action` action delegates to `gradle/actions/wrapper-validation` with the same version.
Configuration and usage of these actions is identical for releases with the same version number.
Since [GitHub Actions](https://github.com/features/actions)
are completely free for open source projects and are automatically enabled on almost all projects,
adding this check to a project's build is as simple as contributing a PR.
Enabling the check requires no overhead on behalf of the project maintainer beyond merging the action.
You can add this action to your favorite Gradle based project without checking out their source locally via the
GitHub Web UI thanks to the 'Create new file' button.
![GitHub 'Create new file' Button bar picture](https://user-images.githubusercontent.com/1323708/73676469-6c023c00-4682-11ea-8c0a-5a1e2d29b17f.png)
Simply add a new file named `.github/workflows/gradle-wrapper-validation.yml` with the contents mentioned above.
We recommend the message commit contents of:
- Title: `Official Gradle Wrapper Validation Action`
- Body (at minimum): `See: https://github.com/gradle/wrapper-validation-action`
From there, you can easily follow the rest of the prompts to create a Pull Request against the project.
## Reporting Failures
If this GitHub action fails because a `gradle-wrapper.jar` doesn't match one of our published SHA-256 checksums,
we highly recommend that you reach out to us at [security@gradle.com](mailto:security@gradle.com).
**Note:** `gradle-wrapper.jar` generated by Gradle 3.3 to 4.0 are not verifiable because those files were dynamically generated by Gradle in a non-reproducible way. It's not possible to verify the `gradle-wrapper.jar` for those versions are legitimate using a hash comparison. You should try to determine if the `gradle-wrapper.jar` was generated by one of these versions before running the build.
If the Gradle version in `gradle-wrapper.properties` is out of this range, you may need to regenerate the `gradle-wrapper.jar` by running `./gradlew wrapper`. If you need to use a version of Gradle between 3.3 and 4.0, you can use a newer version of Gradle to generate the `gradle-wrapper.jar`.
If you're curious and want to explore what the differences are between the `gradle-wrapper.jar` in your possession
and one of our valid release, you can compare them using this online utility: [diffoscope](https://try.diffoscope.org/).
Regardless of what you find, we still kindly request that you reach out to us and let us know.
## Resources
To learn more about verifying the Gradle Wrapper JAR locally, see our
[guide on the topic](https://docs.gradle.org/current/userguide/gradle_wrapper.html#wrapper_checksum_verification).
See the [full wrapper-validation documentation](https://github.com/gradle/actions/tree/main/wrapper-validation) for more details.

View File

@ -1,14 +0,0 @@
# Release
* starting on `main`
* `npm install`
* `npm run all`
* Commit and push any changes to the `dist` directory. Wait for CI.
* `git tag v1.0.x && git push --tags` with the actual version number
* `git tag -f -a v1 && git push -f --tags`
* go to https://github.com/gradle/wrapper-validation-action/releases
* edit and publish the now drafted `v1` release
* create a new release from the new full version number `v1.0.x`, list the fixed issues and publish the release
* go to https://github.com/marketplace/actions/gradle-wrapper-validation
* verify that it displays the latest README
* verify that the version dropdown displays the new version

View File

@ -1,32 +0,0 @@
import * as checksums from '../src/checksums'
import nock from 'nock'
import {afterEach, describe, expect, test, jest} from '@jest/globals'
jest.setTimeout(30000)
test('fetches wrapper jars checksums', async () => {
const validChecksums = await checksums.fetchValidChecksums(false)
expect(validChecksums.length).toBeGreaterThan(10)
})
describe('retry', () => {
afterEach(() => {
nock.cleanAll()
})
describe('for /versions/all API', () => {
test('retry three times', async () => {
nock('https://services.gradle.org', {allowUnmocked: true})
.get('/versions/all')
.times(3)
.replyWithError({
message: 'connect ECONNREFUSED 104.18.191.9:443',
code: 'ECONNREFUSED'
})
const validChecksums = await checksums.fetchValidChecksums(false)
expect(validChecksums.length).toBeGreaterThan(10)
nock.isDone()
})
})
})

View File

@ -1,12 +0,0 @@
import * as path from 'path'
import * as find from '../src/find'
import {expect, test} from '@jest/globals'
test('finds test data wrapper jars', async () => {
const repoRoot = path.resolve('.')
const wrapperJars = await find.findWrapperJars(repoRoot)
expect(wrapperJars.length).toBe(3)
expect(wrapperJars).toContain('__tests__/data/valid/gradle-wrapper.jar')
expect(wrapperJars).toContain('__tests__/data/invalid/gradle-wrapper.jar')
expect(wrapperJars).toContain('__tests__/data/invalid/gradlе-wrapper.jar') // homoglyph
})

View File

@ -1,12 +0,0 @@
import * as path from 'path'
import * as hash from '../src/hash'
import {expect, test} from '@jest/globals'
test('can sha256 files', async () => {
const sha = await hash.sha256File(
path.resolve('__tests__/data/invalid/gradle-wrapper.jar')
)
expect(sha).toEqual(
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
)
})

View File

@ -1,74 +0,0 @@
import * as path from 'path'
import * as validate from '../src/validate'
import {expect, test, jest} from '@jest/globals'
jest.setTimeout(30000)
const baseDir = path.resolve('.')
test('succeeds if all found wrapper jars are valid', async () => {
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
])
expect(result.isValid()).toBe(true)
expect(result.toDisplayString()).toBe(
'✓ Found known Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradlе-wrapper.jar\n' + // homoglyph
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
)
})
test('fails if invalid wrapper jars are found', async () => {
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [])
expect(result.isValid()).toBe(false)
expect(result.valid).toEqual([
new validate.WrapperJar(
'__tests__/data/valid/gradle-wrapper.jar',
'3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce'
)
])
expect(result.invalid).toEqual([
new validate.WrapperJar(
'__tests__/data/invalid/gradle-wrapper.jar',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
),
new validate.WrapperJar(
'__tests__/data/invalid/gradlе-wrapper.jar', // homoglyph
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
)
])
expect(result.toDisplayString()).toBe(
'✗ Found unknown Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradlе-wrapper.jar\n' + // homoglyph
'✓ Found known Gradle Wrapper JAR files:\n' +
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
)
})
test('fails if not enough wrapper jars are found', async () => {
const result = await validate.findInvalidWrapperJars(baseDir, 4, false, [])
expect(result.isValid()).toBe(false)
expect(result.errors).toEqual([
'Expected to find at least 4 Gradle Wrapper JARs but got only 3'
])
expect(result.toDisplayString()).toBe(
'✗ Found unknown Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradlе-wrapper.jar\n' + // homoglyph
'✗ Other validation errors:\n' +
' Expected to find at least 4 Gradle Wrapper JARs but got only 3\n' +
'✓ Found known Gradle Wrapper JAR files:\n' +
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
)
})

View File

@ -18,11 +18,21 @@ inputs:
outputs:
failed-wrapper:
description: The path of the Gradle Wrapper(s) JAR that failed validation.
description: 'The path of the Gradle Wrapper(s) JAR that failed validation. Path is a platform-dependent relative path to git repository root. Multiple paths are separated by a | character.'
value: ${{ steps.wrapper-validation.outputs.failed-wrapper }}
runs:
using: 'node20'
main: 'dist/index.js'
using: "composite"
steps:
- name: Wrapper Validation
id: wrapper-validation
uses: gradle/actions/wrapper-validation@v3.5.0
with:
min-wrapper-count: ${{ inputs.min-wrapper-count }}
allow-snapshots: ${{ inputs.allow-snapshots }}
allow-checksums: ${{ inputs.allow-checksums }}
env:
GRADLE_ACTION_ID: gradle/wrapper-validation-action
branding:
icon: 'shield'

30041
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts', 'json'],
testMatch: ['**/*.test.ts'],
preset: 'ts-jest',
verbose: true,
setupFilesAfterEnv: ['./jest.setup.js']
}

View File

@ -1 +0,0 @@
jest.setTimeout(10000) // in milliseconds

6794
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
{
"name": "wrapper-validation-action",
"version": "0.0.0",
"private": true,
"description": "Gradle Wrapper Validation Action",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
"pack": "ncc build",
"test": "jest",
"all": "npm run build && npm run format && npm run lint && npm run pack && npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/gradle/wrapper-validation-action.git"
},
"keywords": [
"actions",
"node",
"setup"
],
"author": "Gradle Inc.",
"license": "MIT",
"dependencies": {
"@actions/core": "1.10.1",
"typed-rest-client": "1.8.11",
"unhomoglyph": "1.0.6"
},
"devDependencies": {
"@types/node": "16.18.38",
"@typescript-eslint/parser": "6.20.0",
"@vercel/ncc": "0.38.1",
"eslint": "8.56.0",
"eslint-plugin-github": "4.10.1",
"eslint-plugin-jest": "27.6.3",
"eslint-plugin-prettier": "5.1.3",
"glob-parent": "6.0.2",
"jest": "29.7.0",
"js-yaml": "4.1.0",
"nock": "13.5.1",
"prettier": "3.2.4",
"ts-jest": "29.1.2",
"typescript": "5.3.3"
}
}

View File

@ -1,40 +0,0 @@
import * as httpm from 'typed-rest-client/HttpClient'
const httpc = new httpm.HttpClient(
'gradle/wrapper-validation-action',
undefined,
{allowRetries: true, maxRetries: 3}
)
export async function fetchValidChecksums(
allowSnapshots: boolean
): Promise<string[]> {
const all = await httpGetJsonArray('https://services.gradle.org/versions/all')
const withChecksum = all.filter(
entry =>
typeof entry === 'object' &&
entry != null &&
entry.hasOwnProperty('wrapperChecksumUrl')
)
const allowed = withChecksum.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => allowSnapshots || !entry.snapshot
)
const checksumUrls = allowed.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => entry.wrapperChecksumUrl as string
)
const checksums = await Promise.all(
checksumUrls.map(async (url: string) => httpGetText(url))
)
return [...new Set(checksums)]
}
async function httpGetJsonArray(url: string): Promise<unknown[]> {
return JSON.parse(await httpGetText(url))
}
async function httpGetText(url: string): Promise<string> {
const response = await httpc.get(url)
return await response.readBody()
}

View File

@ -1,27 +0,0 @@
import * as util from 'util'
import * as path from 'path'
import * as fs from 'fs'
import unhomoglyph from 'unhomoglyph'
const readdir = util.promisify(fs.readdir)
export async function findWrapperJars(baseDir: string): Promise<string[]> {
const files = await recursivelyListFiles(baseDir)
return files
.filter(file => unhomoglyph(file).endsWith('gradle-wrapper.jar'))
.map(wrapperJar => path.relative(baseDir, wrapperJar))
.sort((a, b) => a.localeCompare(b))
}
async function recursivelyListFiles(baseDir: string): Promise<string[]> {
const childrenNames = await readdir(baseDir)
const childrenPaths = await Promise.all(
childrenNames.map(async childName => {
const childPath = path.resolve(baseDir, childName)
return fs.lstatSync(childPath).isDirectory()
? recursivelyListFiles(childPath)
: new Promise(resolve => resolve([childPath]))
})
)
return Array.prototype.concat(...childrenPaths)
}

View File

@ -1,18 +0,0 @@
import * as crypto from 'crypto'
import * as fs from 'fs'
export async function sha256File(path: string): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256')
const stream = fs.createReadStream(path)
stream.on('data', data => hash.update(data))
stream.on('end', () => {
stream.destroy()
resolve(hash.digest('hex'))
})
stream.on('error', error => {
stream.destroy()
reject(error)
})
})
}

View File

@ -1,36 +0,0 @@
import * as path from 'path'
import * as core from '@actions/core'
import * as validate from './validate'
export async function run(): Promise<void> {
try {
const result = await validate.findInvalidWrapperJars(
path.resolve('.'),
+core.getInput('min-wrapper-count'),
core.getInput('allow-snapshots') === 'true',
core.getInput('allow-checksums').split(',')
)
if (result.isValid()) {
core.info(result.toDisplayString())
} else {
core.setFailed(
`Gradle Wrapper Validation Failed!\n See https://github.com/gradle/wrapper-validation-action#reporting-failures\n${result.toDisplayString()}`
)
if (result.invalid.length > 0) {
core.setOutput(
'failed-wrapper',
`${result.invalid.map(w => w.path).join('|')}`
)
}
}
} catch (error) {
if (error instanceof Error) {
core.setFailed(error.message)
} else {
core.setFailed(`Unknown object was thrown: ${error}`)
}
}
}
run()

View File

@ -1,86 +0,0 @@
import * as find from './find'
import * as checksums from './checksums'
import * as hash from './hash'
export async function findInvalidWrapperJars(
gitRepoRoot: string,
minWrapperCount: number,
allowSnapshots: boolean,
allowChecksums: string[]
): Promise<ValidationResult> {
const wrapperJars = await find.findWrapperJars(gitRepoRoot)
const result = new ValidationResult([], [])
if (wrapperJars.length < minWrapperCount) {
result.errors.push(
`Expected to find at least ${minWrapperCount} Gradle Wrapper JARs but got only ${wrapperJars.length}`
)
}
if (wrapperJars.length > 0) {
const validChecksums = await checksums.fetchValidChecksums(allowSnapshots)
validChecksums.push(...allowChecksums)
for (const wrapperJar of wrapperJars) {
const sha = await hash.sha256File(wrapperJar)
if (!validChecksums.includes(sha)) {
result.invalid.push(new WrapperJar(wrapperJar, sha))
} else {
result.valid.push(new WrapperJar(wrapperJar, sha))
}
}
}
return result
}
export class ValidationResult {
valid: WrapperJar[]
invalid: WrapperJar[]
errors: string[] = []
constructor(valid: WrapperJar[], invalid: WrapperJar[]) {
this.valid = valid
this.invalid = invalid
}
isValid(): boolean {
return this.invalid.length === 0 && this.errors.length === 0
}
toDisplayString(): string {
let displayString = ''
if (this.invalid.length > 0) {
displayString += `✗ Found unknown Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList(
this.invalid
)}`
}
if (this.errors.length > 0) {
if (displayString.length > 0) displayString += '\n'
displayString += `✗ Other validation errors:\n ${this.errors.join(
`\n `
)}`
}
if (this.valid.length > 0) {
if (displayString.length > 0) displayString += '\n'
displayString += `✓ Found known Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList(
this.valid
)}`
}
return displayString
}
private static toDisplayList(wrapperJars: WrapperJar[]): string {
return ` ${wrapperJars.map(wj => wj.toDisplayString()).join(`\n `)}`
}
}
export class WrapperJar {
path: string
checksum: string
constructor(path: string, checksum: string) {
this.path = path
this.checksum = checksum
}
toDisplayString(): string {
return `${this.checksum} ${this.path}`
}
}

View File

@ -1,12 +0,0 @@
{
"compilerOptions": {
"target": "ES2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./lib", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
},
"exclude": ["node_modules", "**/*.test.ts"]
}