Repository: lirantal/lockfile-lint
Pull request #81 (2020-03-21 03:03:28)

fixing a shrinkwrap typo
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->
People want to copy "shrinkwrap", but copy "shrinkwarp" instead. Amusing, but not as fun for them :)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
https://github.com/lirantal/lockfile-lint/issues/80

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
Just trying to help.

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
<!--- Unit tests are expected for all changes. -->
Haven't tested, I don't think it's needed.

## Screenshots (if appropriate):
¯\\\_(ツ)_/¯
## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] I have updated the documentation (if required).
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [x] I added a picture of a cute animal cause it's fun

## My doggo

![image](https://user-images.githubusercontent.com/4380333/77218148-ee5e9680-6b30-11ea-9282-0d38326d16af.png)


codecov-io: # [Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=h1) Report
> Merging [#81](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=desc) into [master](https://codecov.io/gh/lirantal/lockfile-lint/commit/b481e8046cc5b8736263c3ce130c9783677460c5?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/lirantal/lockfile-lint/pull/81/graphs/tree.svg?width=650&token=P36VJa7ksa&height=150&src=pr)](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #81   +/-   ##
=======================================
  Coverage   96.87%   96.87%           
=======================================
  Files          11       11           
  Lines         192      192           
  Branches       31       31           
=======================================
  Hits          186      186           
  Misses          5        5           
  Partials        1        1
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=footer). Last update [b481e80...c96433e](https://codecov.io/gh/lirantal/lockfile-lint/pull/81?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 98624a1..e522dc4 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ npx lockfile-lint --path yarn.lock --allowed-hosts npm yarn --validate-https
 To lint the npm-shrinkwrap.json file, add the `--type npm` flag:
 
 ```bash
-npx lockfile-lint --path npm-shrinkwarp.json --type npm --allowed-hosts npm yarn --validate-https
+npx lockfile-lint --path npm-shrinkwrap.json --type npm --allowed-hosts npm yarn --validate-https
 ```
 
 If you get no results, congratulations, the file passes!
diff --git a/packages/lockfile-lint-api/index.js b/packages/lockfile-lint-api/index.js
index 12adaef..300d4e5 100644
--- a/packages/lockfile-lint-api/index.js
+++ b/packages/lockfile-lint-api/index.js
@@ -3,11 +3,13 @@
 const ValidateHost = require('./src/validators/ValidateHost')
 const ValidateHttps = require('./src/validators/ValidateHttps')
 const ValidateScheme = require('./src/validators/ValidateScheme')
+const ValidateUrl = require('./src/validators/ValidateUrl')
 const ParseLockfile = require('./src/ParseLockfile')
 
 module.exports = {
   ParseLockfile,
   ValidateHost,
   ValidateHttps,
-  ValidateScheme
+  ValidateScheme,
+  ValidateUrl
 }
diff --git a/packages/lockfile-lint-api/src/validators/ValidateHost.js b/packages/lockfile-lint-api/src/validators/ValidateHost.js
index 24390f1..d033396 100644
--- a/packages/lockfile-lint-api/src/validators/ValidateHost.js
+++ b/packages/lockfile-lint-api/src/validators/ValidateHost.js
@@ -28,17 +28,14 @@ module.exports = class ValidateHost {
         continue
       }
 
-      let packageResolvedURL = {}
-
       try {
-        packageResolvedURL = new URL(packageMetadata.resolved)
-
+        const packageResolvedURL = new URL(packageMetadata.resolved)
         const allowedHosts = hosts.map(hostValue => {
           // eslint-disable-next-line security/detect-object-injection
           return REGISTRY[hostValue] ? REGISTRY[hostValue] : hostValue
         })
-
-        if (!allowedHosts.includes(packageResolvedURL.host)) {
+        const isPassing = allowedHosts.includes(packageResolvedURL.host)
+        if (!isPassing) {
           if (!packageResolvedURL.host && options && options.emptyHostname) {
             debug(`detected empty hostname but allowing because emptyHostname is not false`)
           } else {
@@ -61,4 +58,21 @@ module.exports = class ValidateHost {
 
     return validationResult
   }
+
+  validateSingle (packageName, hosts) {
+    // eslint-disable-next-line security/detect-object-injection
+    const packageMetadata = this.packages[packageName]
+
+    if (!('resolved' in packageMetadata)) {
+      return true
+    }
+
+    const packageResolvedURL = new URL(packageMetadata.resolved)
+    const allowedHosts = hosts.map(hostValue => {
+      // eslint-disable-next-line security/detect-object-injection
+      return REGISTRY[hostValue] ? REGISTRY[hostValue] : hostValue
+    })
+
+    return allowedHosts.includes(packageResolvedURL.host)
+  }
 }
diff --git a/packages/lockfile-lint-api/src/validators/ValidateUrl.js b/packages/lockfile-lint-api/src/validators/ValidateUrl.js
new file mode 100644
index 0000000..314236e
--- /dev/null
+++ b/packages/lockfile-lint-api/src/validators/ValidateUrl.js
@@ -0,0 +1,60 @@
+'use strict'
+
+module.exports = class ValidateUrl {
+  constructor ({packages} = {}) {
+    if (typeof packages !== 'object') {
+      throw new Error('expecting an object passed to validator constructor')
+    }
+
+    this.packages = packages
+  }
+
+  validate (allowedUrls, options) {
+    if (!Array.isArray(allowedUrls)) {
+      throw new Error('validate method requires an array')
+    }
+
+    let validationResult = {
+      type: 'success',
+      errors: []
+    }
+
+    for (const [packageName, packageMetadata] of Object.entries(this.packages)) {
+      if (!('resolved' in packageMetadata)) {
+        continue
+      }
+
+      try {
+        const isPassing = allowedUrls.indexOf(packageMetadata.resolved) > -1
+        if (!isPassing) {
+          validationResult.errors.push({
+            message: `detected invalid url(s) for package: ${packageName}\n    expected: ${allowedUrls}\n    actual: ${
+              packageMetadata.resolved
+            }\n`,
+            package: packageName
+          })
+        }
+      } catch (error) {
+        // swallow error (assume that the version is correct)
+      }
+    }
+
+    if (validationResult.errors.length !== 0) {
+      validationResult.type = 'error'
+    }
+
+    return validationResult
+  }
+
+  validateSingle (packageName, allowedUrls) {
+    // eslint-disable-next-line security/detect-object-injection
+    const packageMetadata = this.packages[packageName]
+    if (!('resolved' in packageMetadata)) {
+      return true
+    }
+
+    const resolvedUrl = packageMetadata.resolved
+
+    return allowedUrls.indexOf(resolvedUrl) > -1
+  }
+}
diff --git a/packages/lockfile-lint/bin/lockfile-lint.js b/packages/lockfile-lint/bin/lockfile-lint.js
index 167363b..bf4d993 100755
--- a/packages/lockfile-lint/bin/lockfile-lint.js
+++ b/packages/lockfile-lint/bin/lockfile-lint.js
@@ -18,17 +18,32 @@ let validators = []
 const supportedValidators = new Map([
   ['allowed-hosts', 'validateHosts'],
   ['validate-https', 'validateHttps'],
-  ['allowed-schemes', 'validateSchemes']
+  ['allowed-schemes', 'validateSchemes'],
+  ['allowed-urls', 'validateUrls']
 ])
 
 for (const [commandArgument, commandValue] of Object.entries(config)) {
+  /**
+   * If we have both --allowed-urls and --allowed-hosts flags active
+   * then we can skip doing the work for allowed urls as the validator
+   * for allowed hosts will check for both.
+   *
+   * We only need to run the check for allowed urls if the user does not
+   * specify allowed hosts.
+   */
+  if (commandArgument === 'allowed-urls' && config['allowed-hosts']) {
+    continue
+  }
+
   if (supportedValidators.has(commandArgument)) {
     const validatorItem = supportedValidators.get(commandArgument)
     validators.push({
       name: validatorItem,
       values: commandValue,
       options: {
-        emptyHostname: config['empty-hostname']
+        emptyHostname: config['empty-hostname'],
+        allowedHosts: config['allowed-hosts'],
+        allowedUrls: config['allowed-urls']
       }
     })
   }
diff --git a/packages/lockfile-lint/src/config.js b/packages/lockfile-lint/src/config.js
index ff29ddd..fe619d4 100644
--- a/packages/lockfile-lint/src/config.js
+++ b/packages/lockfile-lint/src/config.js
@@ -62,6 +62,11 @@ module.exports = (argv, exitProcess = false, searchFrom = process.cwd()) => {
         describe:
           'validates a whitelist of allowed schemes to be used for resources in the lockfile',
         conflicts: ['validate-https', 's']
+      },
+      u: {
+        alias: ['allowed-urls'],
+        type: 'array',
+        describe: 'validates a whitelist of allowed URLs to be used for resources in the lockfile'
       }
     })
     .example('lockfile-lint --path yarn.lock --validate-https')
diff --git a/packages/lockfile-lint/src/main.js b/packages/lockfile-lint/src/main.js
index d0a99a0..cb09276 100644
--- a/packages/lockfile-lint/src/main.js
+++ b/packages/lockfile-lint/src/main.js
@@ -4,13 +4,15 @@ const debug = require('debug')('lockfile-lint')
 const {
   ValidateHostManager,
   ValidateHttpsManager,
-  ValidateSchemeManager
+  ValidateSchemeManager,
+  ValidateUrlManager
 } = require('../src/validators')
 
 const validatorFunctions = new Map([
   ['validateHosts', ValidateHostManager],
   ['validateHttps', ValidateHttpsManager],
-  ['validateSchemes', ValidateSchemeManager]
+  ['validateSchemes', ValidateSchemeManager],
+  ['validateUrls', ValidateUrlManager]
 ])
 
 function runValidators ({type, path, validators} = {}) {
diff --git a/packages/lockfile-lint/src/validators/index.js b/packages/lockfile-lint/src/validators/index.js
index f81e485..5b8ad5e 100644
--- a/packages/lockfile-lint/src/validators/index.js
+++ b/packages/lockfile-lint/src/validators/index.js
@@ -1,12 +1,19 @@
 'use strict'
 
-const {ValidateHost, ParseLockfile, ValidateHttps, ValidateScheme} = require('lockfile-lint-api')
+const {
+  ValidateHost,
+  ParseLockfile,
+  ValidateHttps,
+  ValidateScheme,
+  ValidateUrl
+} = require('lockfile-lint-api')
 const debug = require('debug')
 
 module.exports = {
   ValidateHostManager,
   ValidateHttpsManager,
-  ValidateSchemeManager
+  ValidateSchemeManager,
+  ValidateUrlManager
 }
 
 function ValidateSchemeManager ({path, type, validatorValues, validatorOptions}) {
@@ -39,8 +46,23 @@ function ValidateHostManager ({path, type, validatorValues, validatorOptions}) {
   const parser = new ParseLockfile(options)
   const lockfile = parser.parseSync()
   const validator = new ValidateHost({packages: lockfile.object})
+  const validationResult = validator.validate(validatorValues, validatorOptions)
 
-  return validator.validate(validatorValues, validatorOptions)
+  // Check if some of the errors are for allowed URLs and filter those out
+  if (validatorOptions && validatorOptions.allowedUrls) {
+    const urlValidator = new ValidateUrl({packages: lockfile.object})
+
+    validationResult.errors = validationResult.errors.filter(
+      result => !urlValidator.validateSingle(result.package, validatorOptions.allowedUrls)
+    )
+
+    // If we don't have any errors left at this point make sure it's a success type
+    if (!validationResult.errors.length) {
+      validationResult.type = 'success'
+    }
+  }
+
+  return validationResult
 }
 
 function ValidateHttpsManager ({path, type, validatorValues, validatorOptions}) {
@@ -59,3 +81,20 @@ function ValidateHttpsManager ({path, type, validatorValues, validatorOptions})
 
   return validator.validate()
 }
+
+function ValidateUrlManager ({path, type, validatorValues, validatorOptions}) {
+  debug('validate-url-manager')(
+    `invoked with validator options: ${JSON.stringify(validatorValues)}`
+  )
+
+  const options = {
+    lockfilePath: path,
+    lockfileType: type
+  }
+
+  const parser = new ParseLockfile(options)
+  const lockfile = parser.parseSync()
+  const validator = new ValidateUrl({packages: lockfile.object})
+
+  return validator.validate(validatorValues, validatorOptions)
+}


Test patch:
diff --git a/packages/lockfile-lint-api/__tests__/validators.host.test.js b/packages/lockfile-lint-api/__tests__/validators.host.test.js
index 685b6da..8b16d40 100644
--- a/packages/lockfile-lint-api/__tests__/validators.host.test.js
+++ b/packages/lockfile-lint-api/__tests__/validators.host.test.js
@@ -197,4 +197,24 @@ describe('Validator: Host', () => {
       validator.validate(['npm'])
     }).not.toThrow()
   })
+
+  it('validator should return true for a single package on a valid host', () => {
+    const mockedPackages = {
+      '@babel/code-frame': {
+        resolved: 'https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz'
+      }
+    }
+    const validator = new ValidatorHost({packages: mockedPackages})
+
+    expect(validator.validateSingle('@babel/code-frame', ['npm'])).toEqual(true)
+  })
+
+  it('validator should return true for a single package that does not have a resolved URL', () => {
+    const mockedPackages = {
+      meow: {}
+    }
+    const validator = new ValidatorHost({packages: mockedPackages})
+
+    expect(validator.validateSingle('meow', ['npm'])).toEqual(true)
+  })
 })
diff --git a/packages/lockfile-lint-api/__tests__/validators.url.test.js b/packages/lockfile-lint-api/__tests__/validators.url.test.js
new file mode 100644
index 0000000..b6c4d4c
--- /dev/null
+++ b/packages/lockfile-lint-api/__tests__/validators.url.test.js
@@ -0,0 +1,138 @@
+const ValidatorUrl = require('../src/validators/ValidateUrl')
+
+describe('Validator: Url', () => {
+  it('validator should throw an error when provided a string', () => {
+    expect(() => new ValidatorUrl('ss')).toThrowError()
+  })
+
+  it('validator should throw an error when provided null', () => {
+    expect(() => new ValidatorUrl(null)).toThrowError()
+  })
+
+  it('validator should throw an error when provided array', () => {
+    expect(() => new ValidatorUrl(['a'])).toThrowError()
+  })
+
+  it('validator should throw an error instantiated with no value', () => {
+    expect(() => new ValidatorUrl()).toThrowError()
+  })
+
+  it('validator should fail if not allowed url is used for a resource', () => {
+    const mockedPackages = {
+      bolt11: {
+        resolved: 'https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247'
+      }
+    }
+
+    const validator = new ValidatorUrl({packages: mockedPackages})
+    expect(validator.validate(['https://registry.npmjs.org'])).toEqual({
+      type: 'error',
+      errors: [
+        {
+          message: `detected invalid url(s) for package: bolt11\n    expected: https://registry.npmjs.org\n    actual: https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247\n`,
+          package: 'bolt11'
+        }
+      ]
+    })
+  })
+
+  it('validator should succeed if all resources are from an allowed URL', () => {
+    const mockedPackages = {
+      '@babel/code-frame': {
+        resolved: 'https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz'
+      },
+      meow: {
+        resolved: 'https://registry.npmjs.org/meow/-/meow-4.0.1.tgz'
+      },
+      '@babel/generator': {
+        resolved: 'https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz'
+      }
+    }
+
+    const validator = new ValidatorUrl({packages: mockedPackages})
+    expect(
+      validator.validate([
+        'https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz',
+        'https://registry.npmjs.org/meow/-/meow-4.0.1.tgz',
+        'https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz'
+      ])
+    ).toEqual({
+      type: 'success',
+      errors: []
+    })
+  })
+
+  it('validator should fail if validate method receives a non-array value', () => {
+    const mockedPackages = {
+      '@babel/code-frame': {
+        resolved: 'https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz'
+      },
+      meow: {
+        resolved: 'http://registry.npmjs.org/meow/-/meow-4.0.1.tgz'
+      },
+      '@babel/generator': {
+        resolved: 'https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz'
+      }
+    }
+
+    const validator = new ValidatorUrl({packages: mockedPackages})
+    expect(() => {
+      validator.validate(null)
+    }).toThrowError(`validate method requires an array`)
+  })
+
+  it('validator should not throw if package has no `resolved` field', () => {
+    const mockedPackages = {
+      '@babel/code-frame': {
+        resolved: 'https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz'
+      },
+      meow: {}
+    }
+    const validator = new ValidatorUrl({packages: mockedPackages})
+
+    expect(() => {
+      validator.validate(['https://registry.npms.org/@babel/code-frame'])
+    }).not.toThrow()
+  })
+
+  it('validator should return true for a single package with a valid URL', () => {
+    const mockedPackages = {
+      'bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247': {
+        resolved: 'https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247'
+      }
+    }
+    const validator = new ValidatorUrl({packages: mockedPackages})
+
+    expect(
+      validator.validateSingle(
+        'bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247',
+        ['https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247']
+      )
+    ).toEqual(true)
+  })
+
+  it('validator should return false for a single package with an invalid URL', () => {
+    const mockedPackages = {
+      'bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247': {
+        resolved: 'https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247'
+      }
+    }
+    const validator = new ValidatorUrl({packages: mockedPackages})
+
+    expect(
+      validator.validateSingle(
+        'bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247',
+        ['https://github.com/LN-Zap/bolt11']
+      )
+    ).toEqual(false)
+  })
+
+  it('validator should return true for a single package that does not have a resolved URL', () => {
+    const mockedPackages = {
+      meow: {}
+    }
+    const validator = new ValidatorUrl({packages: mockedPackages})
+
+    expect(validator.validateSingle('meow', ['https://github.com/LN-Zap/bolt11'])).toEqual(true)
+  })
+})
diff --git a/packages/lockfile-lint/__tests__/cli.test.js b/packages/lockfile-lint/__tests__/cli.test.js
index 4796009..930ec03 100644
--- a/packages/lockfile-lint/__tests__/cli.test.js
+++ b/packages/lockfile-lint/__tests__/cli.test.js
@@ -4,6 +4,7 @@ const path = require('path')
 // eslint-disable-next-line security/detect-child-process
 const childProcess = require('child_process')
 const cliExecPath = path.join(__dirname, '../bin/lockfile-lint.js')
+const {ValidateHostManager, ValidateUrlManager} = require('../src/validators/index')
 
 describe('CLI tests', () => {
   test('Running without parameters should display help', done => {
@@ -101,6 +102,29 @@ describe('CLI tests', () => {
     })
   })
 
+  test('Allowed hosts and allowed urls flags should work together', done => {
+    const process = childProcess.spawn(cliExecPath, [
+      '--type',
+      'yarn',
+      '--path',
+      '__tests__/fixtures/yarn-and-github-url.lock',
+      '--allowed-hosts',
+      'yarn',
+      '--allowed-urls',
+      'https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247'
+    ])
+
+    let output = ''
+    process.stderr.on('data', chunk => {
+      output += chunk
+    })
+
+    process.stderr.on('close', _ => {
+      expect(output).toBe('')
+      done()
+    })
+  })
+
   describe('cosmiconfig integration', () => {
     it('options are loaded from cosmiconfig files', done => {
       const lintProcess = childProcess.spawn(cliExecPath, [], {
@@ -155,3 +179,58 @@ describe('CLI tests', () => {
     })
   })
 })
+
+describe('Validator managers:', () => {
+  it('Host manager should work together with URL manager', () => {
+    const result = ValidateHostManager({
+      path: '__tests__/fixtures/yarn-and-github-url.lock',
+      type: 'yarn',
+      validatorValues: ['yarn'],
+      validatorOptions: {
+        allowedUrls: ['https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247']
+      }
+    })
+    expect(result).toEqual({
+      type: 'success',
+      errors: []
+    })
+  })
+
+  it('Host manager should return errors for lock file with packages on other hosts', () => {
+    const result = ValidateHostManager({
+      path: '__tests__/fixtures/yarn-and-github-url.lock',
+      type: 'yarn',
+      validatorValues: ['yarn']
+    })
+    expect(result).toEqual({
+      type: 'error',
+      errors: [
+        {
+          message:
+            'detected invalid host(s) for package: bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247\n    expected: registry.yarnpkg.com\n    actual: github.com\n',
+          package:
+            'bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247'
+        }
+      ]
+    })
+  })
+
+  it('URL manager should return errors for lock file with packages on other URLs', () => {
+    const result = ValidateUrlManager({
+      path: '__tests__/fixtures/yarn-and-github-url.lock',
+      type: 'yarn',
+      validatorValues: ['https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247']
+    })
+
+    expect(result).toEqual({
+      type: 'error',
+      errors: [
+        {
+          message:
+            'detected invalid url(s) for package: ms@^2.1.1\n    expected: https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247\n    actual: https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009\n',
+          package: 'ms@^2.1.1'
+        }
+      ]
+    })
+  })
+})
diff --git a/packages/lockfile-lint/__tests__/fixtures/yarn-and-github-url.lock b/packages/lockfile-lint/__tests__/fixtures/yarn-and-github-url.lock
new file mode 100644
index 0000000..2b2372d
--- /dev/null
+++ b/packages/lockfile-lint/__tests__/fixtures/yarn-and-github-url.lock
@@ -0,0 +1,21 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ms@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+"bolt11@https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247":
+  version "1.2.6"
+  resolved "https://github.com/LN-Zap/bolt11#0492874ea9ced4ab49bf0f59a62368687f147247"
+  dependencies:
+    "@types/bn.js" "^4.11.3"
+    bech32 "^1.1.2"
+    bitcoinjs-lib "^3.3.1"
+    bn.js "^4.11.8"
+    coininfo "https://github.com/LN-Zap/coininfo#d7ac7be9c9c4ee74dada187f7762a55887a9c6ca"
+    lodash "^4.17.11"
+    safe-buffer "^5.1.1"
+    secp256k1 "^3.4.0"