Closed JamesSingleton closed 4 years ago
It is because it takes the last dependency and version and adds it to the flattened lockfile. If we take something like
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
This dependency is used in a lot of packages, if I change the first one to be malicious but the rest are fine, then lockfile-lint
will miss it.
@JamesSingleton can you perhaps provide a more complete lock-file with the semver or ansi-regex problem? The problem isn't that lockfile-lint doesn't lints the dependencies but rather that it perhaps builds them incorrectly and then misses something.
Alrighty, I'm able to reproduce. Thanks for the heads up on this 👌
@JamesSingleton if you wanted to take a stab at fixing it and providing a PR I'm happy to guide you through on the change that I had in mind. It's not a very complicated one but the refactor will touch a few places.
In specific I was thinking of:
ParseLockfile.js
will return a flat array list instead of an objectpackagesList
instead of packages
which hints an array list, and opens the possibility in the future to provide a hierarchy structure or otherwise.@lirantal yeah, I was trying to keep everything an object so there wouldn't have to be much refactoring of the validators. However, as I dug more into the code I don't think that would be possible.
Right. The problem with keeping it as an object is that the same package/version would be equal (which is what happening today) so it would be the same entry and not a different one. I think converting to an array would be ok.
Are you gonna send off a PR for this? (no rush)
This is what I was thinking of:
_flattenNpmDepsTree (npmDepsTree, npmDepMap = {}) {
for (const [depName, depMetadata] of Object.entries(npmDepsTree)) {
const depMetadataShortend = {
version: depMetadata.version,
resolved: depMetadata.resolved ? depMetadata.resolved : depMetadata.version,
integrity: depMetadata.integrity,
requires: depMetadata.requires
}
const hashedDepValues = hash(depMetadataShortend)
npmDepMap[`${depName}@${depMetadata.version}-${hashedDepValues}`] = depMetadataShortend
const nestedDepsTree = depMetadata.dependencies
if (nestedDepsTree && Object.keys(nestedDepsTree).length !== 0) {
this._flattenNpmDepsTree(nestedDepsTree, npmDepMap)
}
}
return npmDepMap
}
However, I now run into another issue Encountered error Invalid URL: 1.1.1 in package: "abbrev@1.1.1-8f0b92d6fc7847f731964a4f3ab3b7c1e53edee5"
This is because the dependency looks like this:
"fsevents": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz",
"integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==",
"dev": true,
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1",
"node-pre-gyp": "*"
},
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
},
}
}
I shortened it as this one is roughly 500 lines long.
The error points to this line
let packageResolvedURL = {}
try {
packageResolvedURL = new URL(packageMetadata.resolved)
} catch (error) {
throw new PackageError(packageName, error)
}
Not sure how this is now just coming up now though. Looks like this will happen with any dependency that does not have a true URL for resolved
since resolved
is optional and doing new URL("1.1.1")
is not a valid URL.
@lirantal, let me know your thoughts on that solution and if you can think of a decent solution for the new URL()
issue as I noticed you do resolved: depMetadata.resolved ? depMetadata.resolved : depMetadata.version
to probably catch if someone npm install
a .tgz
file.
So I have it working minus the above issue. Also, there are tests like
it('validator should throw a descriptive error when one is encounterd in a package', () => {
const mockedPackages = {
'@babel/code-frame': {
resolved: 'debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791'
}
}
const validator = new ValidatorHost({packages: mockedPackages})
expect(() => validator.validate(['npm'])).toThrow(PackageError)
})
That yes this throws, but it would throw TypeError [ERR_INVALID_URL]: Invalid URL: debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791
because new URL('debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791'')
is not a valid URL.
The issue with throwing here is that the entire process stops for something that is potentially valid.
One solution I was thinking of was
if (
packageMetadata.resolved.startsWith('file') ||
packageMetadata.resolved.startsWith('http') ||
packageMetadata.resolved.startsWith('git+ssh')
) {
try {
packageResolvedURL = new URL(packageMetadata.resolved)
} catch (error) {
throw new PackageError(packageName, error)
}
}
But not sure about the tests.
If you want to see the changes I had in mind, I have pushed them here: https://github.com/JamesSingleton/lockfile-lint/commits/bugfix/dependency-depth
@lirantal, when do you think this fix will be published?
When running
"test:lockfile": "lockfile-lint -p package-lock.json -t npm -a npm -o https: -c -i",
it does not check past the first resolved URL for a dependency.Expected Behavior
lockfile-lint
should check the entire tree.Current Behavior
If we take the below snippet from a
package-lock.json
and change thesemver
resolved URL tohttp
or a URL other thanregistry.npmjs.org
,lockfile-lint
will not catch this.Possible Solution
At the moment, my current possible solution is to find and loop over all the
resolved
URLs.Steps to Reproduce (for bugs)
lockfile-lint
in it already, create one and installlockfile-lint
lockfile-lint
(e.x."test:lockfile": "lockfile-lint -p package-lock.json -t npm -a npm -o https: -c -i"
)package-lock.json
and change the resolved URL to a dependency that is under"dependencies"
lockfile-lint
Context
I cannot fully lint my entire
package-lock.json
file due to this.Your Environment
^3.0.8
v10.13.0