Merge pull request #6 from JLLeitschuh/feat/JLL/homoglyph_detector

Add a homoglyph detector for gradle-wrapper.jar files
This commit is contained in:
Jonathan Leitschuh 2020-01-15 14:16:32 -05:00 committed by GitHub
commit ffa49e0d93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 79 additions and 13 deletions

View file

@ -34,6 +34,12 @@ verify that any and all `gradle-wrapper.jar` files in the repository match the S
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.
## Usage
Simply add this action to your workflow **after** having checked out your source tree and **before** running any Gradle build:

View file

@ -4,7 +4,8 @@ import * as find from '../src/find'
test('finds test data wrapper jars', async () => {
const repoRoot = path.resolve('.')
const wrapperJars = await find.findWrapperJars(repoRoot)
expect(wrapperJars.length).toBe(2)
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

@ -4,7 +4,7 @@ import * as validate from '../src/validate'
const baseDir = path.resolve('.')
test('succeeds if all found wrapper jars are valid', async () => {
const result = await validate.findInvalidWrapperJars(baseDir, 2, false, [
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
])
@ -13,12 +13,13 @@ test('succeeds if all found wrapper jars are valid', async () => {
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, 2, false, [])
const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [])
expect(result.isValid()).toBe(false)
@ -33,31 +34,37 @@ test('fails if invalid wrapper jars are found', async () => {
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, 3, false, [])
const result = await validate.findInvalidWrapperJars(baseDir, 4, false, [])
expect(result.isValid()).toBe(false)
expect(result.errors).toEqual([
'Expected to find at least 3 Gradle Wrapper JARs but got only 2'
'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 3 Gradle Wrapper JARs but got only 2\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'
)

41
dist/index.js vendored

File diff suppressed because one or more lines are too long

View file

@ -7,5 +7,6 @@ module.exports = {
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
verbose: true,
setupFilesAfterEnv: ['./jest.setup.js']
}

1
jest.setup.js Normal file
View file

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

5
package-lock.json generated
View file

@ -6180,6 +6180,11 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"unhomoglyph": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.3.tgz",
"integrity": "sha512-PC/OAHE8aiTK0Gfmy0PxOlePazRn+BeCM1r4kFtkHgEnkJZgJoI7yD2yUEjsfSdLXKU1FSt/EcIZvNoKazYUTw=="
},
"union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",

View file

@ -26,7 +26,8 @@
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.0",
"typed-rest-client": "^1.7.1"
"typed-rest-client": "^1.7.1",
"unhomoglyph": "^1.0.3"
},
"devDependencies": {
"@types/jest": "^24.0.23",

4
src/declarations.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module 'unhomoglyph' {
function unhomoglyph(input: string): string
export = unhomoglyph
}

View file

@ -1,13 +1,14 @@
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 => file.endsWith('gradle-wrapper.jar'))
.filter(file => unhomoglyph(file).endsWith('gradle-wrapper.jar'))
.map(wrapperJar => path.relative(baseDir, wrapperJar))
.sort((a, b) => a.localeCompare(b))
}