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. 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 ## Usage
Simply add this action to your workflow **after** having checked out your source tree and **before** running any Gradle build: 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 () => { test('finds test data wrapper jars', async () => {
const repoRoot = path.resolve('.') const repoRoot = path.resolve('.')
const wrapperJars = await find.findWrapperJars(repoRoot) 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/valid/gradle-wrapper.jar')
expect(wrapperJars).toContain('__tests__/data/invalid/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('.') const baseDir = path.resolve('.')
test('succeeds if all found wrapper jars are valid', async () => { 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' 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
]) ])
@ -12,13 +12,14 @@ test('succeeds if all found wrapper jars are valid', async () => {
expect(result.toDisplayString()).toBe( expect(result.toDisplayString()).toBe(
'✓ Found known Gradle Wrapper JAR files:\n' + '✓ Found known Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\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' ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
) )
}) })
test('fails if invalid wrapper jars are found', async () => { 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) expect(result.isValid()).toBe(false)
@ -33,31 +34,37 @@ test('fails if invalid wrapper jars are found', async () => {
new validate.WrapperJar( new validate.WrapperJar(
'__tests__/data/invalid/gradle-wrapper.jar', '__tests__/data/invalid/gradle-wrapper.jar',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
),
new validate.WrapperJar(
'__tests__/data/invalid/gradlе-wrapper.jar', // homoglyph
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
) )
]) ])
expect(result.toDisplayString()).toBe( expect(result.toDisplayString()).toBe(
'✗ Found unknown Gradle Wrapper JAR files:\n' + '✗ Found unknown Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\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' + '✓ Found known Gradle Wrapper JAR files:\n' +
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar' ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar'
) )
}) })
test('fails if not enough wrapper jars are found', async () => { 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.isValid()).toBe(false)
expect(result.errors).toEqual([ 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( expect(result.toDisplayString()).toBe(
'✗ Found unknown Gradle Wrapper JAR files:\n' + '✗ Found unknown Gradle Wrapper JAR files:\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradle-wrapper.jar\n' +
' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 __tests__/data/invalid/gradlе-wrapper.jar\n' + // homoglyph
'✗ Other validation errors:\n' + '✗ 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' + '✓ Found known Gradle Wrapper JAR files:\n' +
' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce __tests__/data/valid/gradle-wrapper.jar' ' 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: { transform: {
'^.+\\.ts$': 'ts-jest' '^.+\\.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", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" "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": { "union-value": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",

View file

@ -26,7 +26,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.0", "@actions/core": "^1.2.0",
"typed-rest-client": "^1.7.1" "typed-rest-client": "^1.7.1",
"unhomoglyph": "^1.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^24.0.23", "@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 util from 'util'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import unhomoglyph from 'unhomoglyph'
const readdir = util.promisify(fs.readdir) const readdir = util.promisify(fs.readdir)
export async function findWrapperJars(baseDir: string): Promise<string[]> { export async function findWrapperJars(baseDir: string): Promise<string[]> {
const files = await recursivelyListFiles(baseDir) const files = await recursivelyListFiles(baseDir)
return files return files
.filter(file => file.endsWith('gradle-wrapper.jar')) .filter(file => unhomoglyph(file).endsWith('gradle-wrapper.jar'))
.map(wrapperJar => path.relative(baseDir, wrapperJar)) .map(wrapperJar => path.relative(baseDir, wrapperJar))
.sort((a, b) => a.localeCompare(b)) .sort((a, b) => a.localeCompare(b))
} }