npq 3.10.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +27 -20
- package/.editorconfig +0 -49
- package/.github/ISSUE_TEMPLATE.md +0 -32
- package/.github/Logo Horizontal.png +0 -0
- package/.github/Logo Vertical.png +0 -0
- package/.github/Logo.png +0 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -35
- package/.github/npq-demo-1.gif +0 -0
- package/.github/npq-demo-1.png +0 -0
- package/.github/npq-demo.gif +0 -0
- package/.github/npq.mov +0 -0
- package/.github/npq.mp4 +0 -0
- package/.github/npq.png +0 -0
- package/.github/workflows/automerge.yml +0 -39
- package/.github/workflows/main.yml +0 -58
- package/.husky/commit-msg +0 -4
- package/.husky/post-merge +0 -4
- package/.husky/pre-commit +0 -4
- package/.husky/pre-push +0 -4
- package/.prettierrc.js +0 -9
- package/CHANGELOG.md +0 -27
- package/CODE_OF_CONDUCT.md +0 -73
- package/CONTRIBUTING.md +0 -55
- package/SECURITY.md +0 -23
- package/__tests__/__fixtures__/test.marshall.js +0 -58
- package/__tests__/cli.parser.test.js +0 -121
- package/__tests__/cli.test.js +0 -110
- package/__tests__/cliPrompt.test.js +0 -409
- package/__tests__/env-var-integration.test.js +0 -109
- package/__tests__/marshalls.base.test.js +0 -113
- package/__tests__/marshalls.classMethods.test.js +0 -34
- package/__tests__/marshalls.expiredDomains.test.js +0 -100
- package/__tests__/marshalls.newBin.test.js +0 -441
- package/__tests__/marshalls.provenance.test.js +0 -383
- package/__tests__/marshalls.repo.test.js +0 -126
- package/__tests__/marshalls.signatures.test.js +0 -132
- package/__tests__/marshalls.snyk.test.js +0 -294
- package/__tests__/marshalls.tasks.test.js +0 -130
- package/__tests__/marshalls.typosquatting.test.js +0 -76
- package/__tests__/marshalls.version-maturity.test.js +0 -194
- package/__tests__/mocks/registryPackageOk.mock.json +0 -146
- package/__tests__/mocks/registryPackageUnpublished.mock.json +0 -31
- package/__tests__/packageManager.test.js +0 -93
- package/__tests__/packageRepoUtils.test.js +0 -251
- package/__tests__/reportResults.test.js +0 -772
- package/__tests__/scripts.test.js +0 -146
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Marshall = require('../lib/marshalls/snyk.marshall.js')
|
|
4
|
-
const { marshallCategories } = require('../lib/marshalls/constants')
|
|
5
|
-
|
|
6
|
-
const fs = require('fs')
|
|
7
|
-
const os = require('os')
|
|
8
|
-
|
|
9
|
-
jest.mock('fs')
|
|
10
|
-
jest.mock('os')
|
|
11
|
-
|
|
12
|
-
os.homedir.mockReturnValue('/fake/home')
|
|
13
|
-
fs.statSync.mockImplementation(() => {
|
|
14
|
-
throw new Error('File not found')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
// Mock fetch globally
|
|
18
|
-
global.fetch = jest.fn()
|
|
19
|
-
|
|
20
|
-
const mockPackageRepoUtils = {
|
|
21
|
-
getLatestVersion: jest.fn().mockResolvedValue('1.0.0')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe('Snyk Marshall', () => {
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
os.homedir.mockReturnValue('/fake/home')
|
|
27
|
-
fetch.mockClear()
|
|
28
|
-
mockPackageRepoUtils.getLatestVersion.mockClear()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
describe('with Snyk token', () => {
|
|
32
|
-
const marshall = new Marshall({
|
|
33
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
34
|
-
})
|
|
35
|
-
// Manually set the token for this test suite
|
|
36
|
-
marshall.snykApiToken = 'fake-snyk-token'
|
|
37
|
-
|
|
38
|
-
it('has the right title', () => {
|
|
39
|
-
expect(marshall.title()).toBe('Checking for known vulnerabilities')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should throw an error if vulnerabilities are found', async () => {
|
|
43
|
-
fetch.mockResolvedValueOnce({
|
|
44
|
-
ok: true,
|
|
45
|
-
json: () =>
|
|
46
|
-
Promise.resolve({
|
|
47
|
-
vulnerabilities: [{ title: 'XSS' }, { title: 'CSRF' }],
|
|
48
|
-
isMaliciousPackage: false
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
|
|
53
|
-
await expect(marshall.validate(pkg)).rejects.toThrow(
|
|
54
|
-
'2 vulnerable path(s) found: https://snyk.io/vuln/npm:test-pkg'
|
|
55
|
-
)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should throw a specific error for malicious packages', async () => {
|
|
59
|
-
fetch.mockResolvedValueOnce({
|
|
60
|
-
ok: true,
|
|
61
|
-
json: () =>
|
|
62
|
-
Promise.resolve({
|
|
63
|
-
vulnerabilities: [{ title: 'Malicious Package' }],
|
|
64
|
-
isMaliciousPackage: true
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const pkg = { packageName: 'malicious-pkg', packageVersion: '1.0.0' }
|
|
69
|
-
await expect(marshall.validate(pkg)).rejects.toThrow(
|
|
70
|
-
'Malicious package found: https://snyk.io/vuln/npm:malicious-pkg'
|
|
71
|
-
)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('should pass if no vulnerabilities are found', async () => {
|
|
75
|
-
fetch.mockResolvedValueOnce({
|
|
76
|
-
ok: true,
|
|
77
|
-
json: () =>
|
|
78
|
-
Promise.resolve({
|
|
79
|
-
vulnerabilities: [],
|
|
80
|
-
isMaliciousPackage: false
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const pkg = { packageName: 'clean-pkg', packageVersion: '1.0.0' }
|
|
85
|
-
await expect(marshall.validate(pkg)).resolves.not.toThrow()
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('should throw an error if the Snyk API request fails', async () => {
|
|
89
|
-
fetch.mockResolvedValueOnce({
|
|
90
|
-
ok: false,
|
|
91
|
-
status: 500
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
|
|
95
|
-
await expect(marshall.validate(pkg)).rejects.toThrow(
|
|
96
|
-
'Snyk API request failed with status 500'
|
|
97
|
-
)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should throw an error if Snyk API provides no vulnerability info', async () => {
|
|
101
|
-
fetch.mockResolvedValueOnce({
|
|
102
|
-
ok: true,
|
|
103
|
-
json: () => Promise.resolve({ message: 'no vulns here' }) // No 'vulnerabilities' property
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
|
|
107
|
-
// This will cause getSnykVulnInfo to return `false`, which makes validate throw
|
|
108
|
-
await expect(marshall.validate(pkg)).rejects.toThrow(
|
|
109
|
-
'Unable to query vulnerabilities for packages'
|
|
110
|
-
)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('should use latest version if packageVersion is not defined', async () => {
|
|
114
|
-
fetch.mockResolvedValueOnce({
|
|
115
|
-
ok: true,
|
|
116
|
-
json: () =>
|
|
117
|
-
Promise.resolve({
|
|
118
|
-
vulnerabilities: [],
|
|
119
|
-
isMaliciousPackage: false
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const pkg = { packageName: 'test-pkg' }
|
|
124
|
-
await marshall.validate(pkg)
|
|
125
|
-
|
|
126
|
-
expect(mockPackageRepoUtils.getLatestVersion).toHaveBeenCalledWith('test-pkg')
|
|
127
|
-
})
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
describe('without Snyk token (fallback to OSV)', () => {
|
|
131
|
-
const marshall = new Marshall({
|
|
132
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
133
|
-
})
|
|
134
|
-
// Ensure no token is set
|
|
135
|
-
marshall.snykApiToken = null
|
|
136
|
-
|
|
137
|
-
it('should throw an error if vulnerabilities are found by OSV', async () => {
|
|
138
|
-
fetch.mockResolvedValueOnce({
|
|
139
|
-
ok: true,
|
|
140
|
-
json: () => Promise.resolve({ vulns: [{ id: 'OSV-1' }, { id: 'OSV-2' }] })
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
const pkg = { packageName: 'test-pkg-osv', packageVersion: '1.0.0' }
|
|
144
|
-
await expect(marshall.validate(pkg)).rejects.toThrow(
|
|
145
|
-
'2 vulnerabilities found by OSV for test-pkg-osv'
|
|
146
|
-
)
|
|
147
|
-
expect(fetch).toHaveBeenCalledWith('https://api.osv.dev/v1/query', expect.any(Object))
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('should pass if no vulnerabilities are found by OSV', async () => {
|
|
151
|
-
fetch.mockResolvedValueOnce({
|
|
152
|
-
ok: true,
|
|
153
|
-
json: () => Promise.resolve({ vulns: [] })
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const pkg = { packageName: 'clean-pkg-osv', packageVersion: '1.0.0' }
|
|
157
|
-
await expect(marshall.validate(pkg)).resolves.not.toThrow()
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('should handle an empty response from OSV', async () => {
|
|
161
|
-
fetch.mockResolvedValueOnce({
|
|
162
|
-
ok: true,
|
|
163
|
-
json: () => Promise.resolve({})
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const pkg = { packageName: 'empty-res-pkg', packageVersion: '1.0.0' }
|
|
167
|
-
await expect(marshall.validate(pkg)).resolves.toEqual({
|
|
168
|
-
issuesCount: 0,
|
|
169
|
-
isMaliciousPackage: false
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it('should handle OSV API fetch error', async () => {
|
|
174
|
-
fetch.mockRejectedValueOnce(new Error('Network error'))
|
|
175
|
-
|
|
176
|
-
const pkg = { packageName: 'fetch-err-pkg', packageVersion: '1.0.0' }
|
|
177
|
-
const result = await marshall.validate(pkg)
|
|
178
|
-
|
|
179
|
-
expect(result).toEqual({ issuesCount: 0, isMaliciousPackage: false })
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
describe('run method', () => {
|
|
184
|
-
const marshall = new Marshall({
|
|
185
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('should call checkPackage for each package', async () => {
|
|
189
|
-
const ctx = {
|
|
190
|
-
pkgs: [
|
|
191
|
-
{ packageName: 'pkg1', packageVersion: '1.0.0' },
|
|
192
|
-
{ packageName: 'pkg2', packageVersion: '2.0.0' }
|
|
193
|
-
]
|
|
194
|
-
}
|
|
195
|
-
const task = {}
|
|
196
|
-
marshall.checkPackage = jest.fn()
|
|
197
|
-
|
|
198
|
-
await marshall.run(ctx, task)
|
|
199
|
-
|
|
200
|
-
expect(marshall.checkPackage).toHaveBeenCalledTimes(2)
|
|
201
|
-
expect(marshall.checkPackage).toHaveBeenCalledWith(ctx.pkgs[0], ctx, task)
|
|
202
|
-
expect(marshall.checkPackage).toHaveBeenCalledWith(ctx.pkgs[1], ctx, task)
|
|
203
|
-
})
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
describe('getSnykToken', () => {
|
|
207
|
-
it('should return null if config file does not exist', () => {
|
|
208
|
-
fs.statSync.mockImplementation(() => {
|
|
209
|
-
throw new Error('ENOENT')
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
const marshall = new Marshall({
|
|
213
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
214
|
-
})
|
|
215
|
-
marshall.snykApiToken = null // ensure no token
|
|
216
|
-
const token = marshall.getSnykToken()
|
|
217
|
-
expect(token).toBeNull()
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it('should return null if config file exists but has no token', () => {
|
|
221
|
-
fs.statSync.mockReturnValueOnce({ isFile: () => true })
|
|
222
|
-
jest.doMock('/fake/home/.config/configstore/snyk.json', () => ({}), { virtual: true })
|
|
223
|
-
|
|
224
|
-
const marshall = new Marshall({
|
|
225
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
226
|
-
})
|
|
227
|
-
marshall.snykApiToken = null // ensure no token
|
|
228
|
-
const token = marshall.getSnykToken()
|
|
229
|
-
expect(token).toBeNull()
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('should return the token if the config file exists and has a token', () => {
|
|
233
|
-
fs.statSync.mockImplementation(() => ({ isFile: () => true }))
|
|
234
|
-
fs.readFileSync.mockReturnValue(JSON.stringify({ api: 'a-real-token' }))
|
|
235
|
-
|
|
236
|
-
const marshall = new Marshall({
|
|
237
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
238
|
-
})
|
|
239
|
-
marshall.snykApiToken = null // ensure no token
|
|
240
|
-
const token = marshall.getSnykToken()
|
|
241
|
-
expect(token).toBe('a-real-token')
|
|
242
|
-
|
|
243
|
-
// Reset mock to avoid affecting other tests
|
|
244
|
-
fs.statSync.mockImplementation(() => {
|
|
245
|
-
throw new Error('File not found')
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('should return null if config file is malformed', () => {
|
|
250
|
-
fs.statSync.mockImplementation(() => ({ isFile: () => true }))
|
|
251
|
-
fs.readFileSync.mockReturnValue('not a valid json')
|
|
252
|
-
|
|
253
|
-
const marshall = new Marshall({
|
|
254
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
255
|
-
})
|
|
256
|
-
marshall.snykApiToken = null // ensure no token
|
|
257
|
-
const token = marshall.getSnykToken()
|
|
258
|
-
expect(token).toBeNull()
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should return null if statSync returns falsy', () => {
|
|
262
|
-
fs.statSync.mockReturnValue(false)
|
|
263
|
-
|
|
264
|
-
const marshall = new Marshall({
|
|
265
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
266
|
-
})
|
|
267
|
-
marshall.snykApiToken = null // ensure no token
|
|
268
|
-
const token = marshall.getSnykToken()
|
|
269
|
-
expect(token).toBeNull()
|
|
270
|
-
})
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
describe('getSnykToken with env var', () => {
|
|
274
|
-
const OLD_ENV = process.env
|
|
275
|
-
|
|
276
|
-
beforeEach(() => {
|
|
277
|
-
jest.resetModules() // Most important - it clears the cache
|
|
278
|
-
process.env = { ...OLD_ENV } // Make a copy
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
afterAll(() => {
|
|
282
|
-
process.env = OLD_ENV // Restore old environment
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('should return token from SNYK_TOKEN environment variable', () => {
|
|
286
|
-
process.env.SNYK_TOKEN = 'token-from-env'
|
|
287
|
-
const MarshallWithEnv = require('../lib/marshalls/snyk.marshall.js')
|
|
288
|
-
const marshall = new MarshallWithEnv({
|
|
289
|
-
packageRepoUtils: mockPackageRepoUtils
|
|
290
|
-
})
|
|
291
|
-
expect(marshall.getSnykToken()).toBe('token-from-env')
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
})
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const marshalls = require('../lib/marshalls')
|
|
3
|
-
|
|
4
|
-
const PackageRepoUtilsMock = class Fake {
|
|
5
|
-
getPackageInfo() {
|
|
6
|
-
return Promise.resolve(true)
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
test('running marshall tasks succeeds', async () => {
|
|
11
|
-
marshalls.collectMarshalls = jest.fn(() => {
|
|
12
|
-
return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const config = {
|
|
16
|
-
pkgs: [{ packageName: 'express' }, { packageName: 'semver' }],
|
|
17
|
-
packageRepoUtils: new PackageRepoUtilsMock()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const results = await marshalls.tasks(config)
|
|
21
|
-
|
|
22
|
-
expect(results[0]['test.marshall']).toEqual({
|
|
23
|
-
status: null,
|
|
24
|
-
errors: [],
|
|
25
|
-
warnings: [],
|
|
26
|
-
data: { express: 'mock data check', semver: 'mock data check' },
|
|
27
|
-
marshall: 'test.marshall',
|
|
28
|
-
categoryId: 'PackageHealth'
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('running marshall tasks fails', async () => {
|
|
33
|
-
marshalls.collectMarshalls = jest.fn(() => {
|
|
34
|
-
return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const config = {
|
|
38
|
-
pkgs: [{ packageName: 'express' }, { packageName: 'dockly' }],
|
|
39
|
-
packageRepoUtils: new PackageRepoUtilsMock()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const mockedResults = [
|
|
43
|
-
{
|
|
44
|
-
'test.marshall': {
|
|
45
|
-
status: null,
|
|
46
|
-
errors: [{ pkg: 'dockly', message: 'simulating mock error' }],
|
|
47
|
-
warnings: [],
|
|
48
|
-
data: { express: 'mock data check' }
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
await expect(marshalls.tasks(config)).resolves.toMatchObject(mockedResults)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('running marshall tasks includes an error when single package is not found', async () => {
|
|
57
|
-
const PackageRepoUtilsNotFoundMock = class Fake {
|
|
58
|
-
getPackageInfo() {
|
|
59
|
-
return Promise.resolve({ error: 'Not found' })
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
marshalls.collectMarshalls = jest.fn(() => {
|
|
64
|
-
return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const config = {
|
|
68
|
-
pkgs: [{ packageName: 'nonexistent-package' }],
|
|
69
|
-
packageRepoUtils: new PackageRepoUtilsNotFoundMock()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const mockedResults = [
|
|
73
|
-
{
|
|
74
|
-
not_found: {
|
|
75
|
-
status: null,
|
|
76
|
-
errors: [{ pkg: 'nonexistent-package', message: 'Package not found' }],
|
|
77
|
-
warnings: [],
|
|
78
|
-
data: {},
|
|
79
|
-
marshall: 'not_found',
|
|
80
|
-
categoryId: 'PackageHealth'
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
await expect(marshalls.tasks(config)).resolves.toMatchObject(mockedResults)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('running marshall tasks filters out not found packages when multiple packages provided', async () => {
|
|
89
|
-
const PackageRepoUtilsMixedMock = class Fake {
|
|
90
|
-
getPackageInfo(packageName) {
|
|
91
|
-
if (packageName === 'nonexistent-package') {
|
|
92
|
-
return Promise.resolve({ error: 'Not found' })
|
|
93
|
-
}
|
|
94
|
-
return Promise.resolve(true)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
marshalls.collectMarshalls = jest.fn(() => {
|
|
99
|
-
return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const config = {
|
|
103
|
-
pkgs: [
|
|
104
|
-
{ packageName: 'express' },
|
|
105
|
-
{ packageName: 'nonexistent-package' },
|
|
106
|
-
{ packageName: 'semver' }
|
|
107
|
-
],
|
|
108
|
-
packageRepoUtils: new PackageRepoUtilsMixedMock()
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const result = await marshalls.tasks(config)
|
|
112
|
-
|
|
113
|
-
expect(result[0]['not_found']).toEqual({
|
|
114
|
-
status: null,
|
|
115
|
-
errors: [{ pkg: 'nonexistent-package', message: 'Package not found' }],
|
|
116
|
-
warnings: [],
|
|
117
|
-
data: {},
|
|
118
|
-
marshall: 'not_found',
|
|
119
|
-
categoryId: 'PackageHealth'
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
expect(result[1]['test.marshall']).toEqual({
|
|
123
|
-
status: null,
|
|
124
|
-
errors: [],
|
|
125
|
-
warnings: [],
|
|
126
|
-
data: { express: 'mock data check', semver: 'mock data check' },
|
|
127
|
-
marshall: 'test.marshall',
|
|
128
|
-
categoryId: 'PackageHealth'
|
|
129
|
-
})
|
|
130
|
-
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const TyposquattingMarshall = require('../lib/marshalls/typosquatting.marshall')
|
|
2
|
-
|
|
3
|
-
describe('Typosquatting Marshall', () => {
|
|
4
|
-
test('should remove duplicate entries from similar packages', async () => {
|
|
5
|
-
const typosquattingMarshall = new TyposquattingMarshall({
|
|
6
|
-
packageRepoUtils: {
|
|
7
|
-
isPackageInAllowList: jest.fn(() => {
|
|
8
|
-
return false // Simulate that the package is not in the allow list
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
// Create a test package that would match multiple similar packages
|
|
14
|
-
const pkg = {
|
|
15
|
-
packageName: 'ghtml' // This should match 'html' which appears multiple times in the data
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
await typosquattingMarshall.validate(pkg)
|
|
20
|
-
// If no error is thrown, the test should fail
|
|
21
|
-
expect(true).toBe(false)
|
|
22
|
-
} catch (error) {
|
|
23
|
-
// Check that the error message doesn't contain duplicate entries
|
|
24
|
-
const errorMessage = error.message
|
|
25
|
-
expect(errorMessage).toContain('Potential typosquatting with popular package(s):')
|
|
26
|
-
|
|
27
|
-
// Extract the package names from the error message
|
|
28
|
-
const packagesList = errorMessage.split('popular package(s): ')[1]
|
|
29
|
-
const packages = packagesList.split(', ')
|
|
30
|
-
|
|
31
|
-
// Check that there are no duplicates
|
|
32
|
-
const uniquePackages = [...new Set(packages)]
|
|
33
|
-
expect(packages.length).toBe(uniquePackages.length)
|
|
34
|
-
|
|
35
|
-
// Verify that 'html' appears only once even though it exists multiple times in the data
|
|
36
|
-
const htmlCount = packages.filter((pkg) => pkg === 'html').length
|
|
37
|
-
expect(htmlCount).toBe(1)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('should not report typosquatting for packages in top packages list', async () => {
|
|
42
|
-
const typosquattingMarshall = new TyposquattingMarshall({
|
|
43
|
-
packageRepoUtils: {
|
|
44
|
-
isPackageInAllowList: jest.fn(() => {
|
|
45
|
-
return true
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// Test with a package that exists in the top packages list
|
|
51
|
-
const pkg = {
|
|
52
|
-
packageName: 'express' // This should be in the top packages list
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const result = await typosquattingMarshall.validate(pkg)
|
|
56
|
-
expect(result).toEqual([])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('should not report typosquatting for packages with no similar matches', async () => {
|
|
60
|
-
const typosquattingMarshall = new TyposquattingMarshall({
|
|
61
|
-
packageRepoUtils: {
|
|
62
|
-
isPackageInAllowList: jest.fn(() => {
|
|
63
|
-
return false // Simulate that the package is not in the allow list
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Test with a package that has no similar matches
|
|
69
|
-
const pkg = {
|
|
70
|
-
packageName: 'verylonganduniquenamethatdoesnotmatchanything'
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = await typosquattingMarshall.validate(pkg)
|
|
74
|
-
expect(result).toEqual([])
|
|
75
|
-
})
|
|
76
|
-
})
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Marshall = require('../lib/marshalls/version-maturity.marshall')
|
|
4
|
-
|
|
5
|
-
describe('Version Maturity Marshall', () => {
|
|
6
|
-
test('should have the correct title', () => {
|
|
7
|
-
const testMarshall = new Marshall({
|
|
8
|
-
packageRepoUtils: null
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
expect(testMarshall.title()).toEqual('Checking version maturity')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
test('should throw error for recently published version (same day)', async () => {
|
|
15
|
-
const now = new Date()
|
|
16
|
-
const hoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000) // 2 hours ago
|
|
17
|
-
|
|
18
|
-
const testMarshall = new Marshall({
|
|
19
|
-
packageRepoUtils: {
|
|
20
|
-
getPackageInfo: () => {
|
|
21
|
-
return Promise.resolve({
|
|
22
|
-
time: {
|
|
23
|
-
'1.0.0': hoursAgo.toISOString()
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
},
|
|
27
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
await expect(
|
|
32
|
-
testMarshall.validate({
|
|
33
|
-
packageName: 'test-package',
|
|
34
|
-
packageVersion: '1.0.0'
|
|
35
|
-
})
|
|
36
|
-
).rejects.toThrow(
|
|
37
|
-
'Detected a recently published version (published 2 hours ago) - consider waiting for community review'
|
|
38
|
-
)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('should throw error for recently published version (within threshold)', async () => {
|
|
42
|
-
const now = new Date()
|
|
43
|
-
const daysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000) // 3 days ago
|
|
44
|
-
|
|
45
|
-
const testMarshall = new Marshall({
|
|
46
|
-
packageRepoUtils: {
|
|
47
|
-
getPackageInfo: () => {
|
|
48
|
-
return Promise.resolve({
|
|
49
|
-
time: {
|
|
50
|
-
'1.0.0': daysAgo.toISOString()
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
},
|
|
54
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
await expect(
|
|
59
|
-
testMarshall.validate({
|
|
60
|
-
packageName: 'test-package',
|
|
61
|
-
packageVersion: '1.0.0'
|
|
62
|
-
})
|
|
63
|
-
).rejects.toThrow(
|
|
64
|
-
'Detected a recently published version (published 3 days ago) - consider waiting for community review'
|
|
65
|
-
)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test('should throw error for version published exactly 1 day ago', async () => {
|
|
69
|
-
const now = new Date()
|
|
70
|
-
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000) // 1 day ago
|
|
71
|
-
|
|
72
|
-
const testMarshall = new Marshall({
|
|
73
|
-
packageRepoUtils: {
|
|
74
|
-
getPackageInfo: () => {
|
|
75
|
-
return Promise.resolve({
|
|
76
|
-
time: {
|
|
77
|
-
'1.0.0': oneDayAgo.toISOString()
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
},
|
|
81
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
await expect(
|
|
86
|
-
testMarshall.validate({
|
|
87
|
-
packageName: 'test-package',
|
|
88
|
-
packageVersion: '1.0.0'
|
|
89
|
-
})
|
|
90
|
-
).rejects.toThrow(
|
|
91
|
-
'Detected a recently published version (published 1 day ago) - consider waiting for community review'
|
|
92
|
-
)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
test('should pass for version published beyond threshold', async () => {
|
|
96
|
-
const now = new Date()
|
|
97
|
-
const weekAgo = new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000) // 8 days ago
|
|
98
|
-
|
|
99
|
-
const testMarshall = new Marshall({
|
|
100
|
-
packageRepoUtils: {
|
|
101
|
-
getPackageInfo: () => {
|
|
102
|
-
return Promise.resolve({
|
|
103
|
-
time: {
|
|
104
|
-
'1.0.0': weekAgo.toISOString()
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
},
|
|
108
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const result = await testMarshall.validate({
|
|
113
|
-
packageName: 'test-package',
|
|
114
|
-
packageVersion: '1.0.0'
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
expect(result).toEqual({
|
|
118
|
-
packageName: 'test-package',
|
|
119
|
-
packageVersion: '1.0.0'
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
test('should throw error when package time information is missing', async () => {
|
|
124
|
-
const testMarshall = new Marshall({
|
|
125
|
-
packageRepoUtils: {
|
|
126
|
-
getPackageInfo: () => {
|
|
127
|
-
return Promise.resolve({})
|
|
128
|
-
},
|
|
129
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
await expect(
|
|
134
|
-
testMarshall.validate({
|
|
135
|
-
packageName: 'test-package',
|
|
136
|
-
packageVersion: '1.0.0'
|
|
137
|
-
})
|
|
138
|
-
).rejects.toThrow('Could not determine package version information')
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
test('should throw error when version release date is missing', async () => {
|
|
142
|
-
const testMarshall = new Marshall({
|
|
143
|
-
packageRepoUtils: {
|
|
144
|
-
getPackageInfo: () => {
|
|
145
|
-
return Promise.resolve({
|
|
146
|
-
time: {
|
|
147
|
-
'2.0.0': new Date().toISOString()
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
},
|
|
151
|
-
getSemVer: () => Promise.resolve('1.0.0')
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
await expect(
|
|
156
|
-
testMarshall.validate({
|
|
157
|
-
packageName: 'test-package',
|
|
158
|
-
packageVersion: '1.0.0'
|
|
159
|
-
})
|
|
160
|
-
).rejects.toThrow('Could not determine release date for version 1.0.0')
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
test('should handle version aliases correctly', async () => {
|
|
164
|
-
const now = new Date()
|
|
165
|
-
const daysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) // 2 days ago
|
|
166
|
-
|
|
167
|
-
const testMarshall = new Marshall({
|
|
168
|
-
packageRepoUtils: {
|
|
169
|
-
getPackageInfo: () => {
|
|
170
|
-
return Promise.resolve({
|
|
171
|
-
time: {
|
|
172
|
-
'1.2.3': daysAgo.toISOString()
|
|
173
|
-
}
|
|
174
|
-
})
|
|
175
|
-
},
|
|
176
|
-
getSemVer: (packageName, packageVersion) => {
|
|
177
|
-
if (packageVersion === 'latest') {
|
|
178
|
-
return Promise.resolve('1.2.3')
|
|
179
|
-
}
|
|
180
|
-
return Promise.resolve(packageVersion)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
await expect(
|
|
186
|
-
testMarshall.validate({
|
|
187
|
-
packageName: 'test-package',
|
|
188
|
-
packageVersion: 'latest'
|
|
189
|
-
})
|
|
190
|
-
).rejects.toThrow(
|
|
191
|
-
'Detected a recently published version (published 2 days ago) - consider waiting for community review'
|
|
192
|
-
)
|
|
193
|
-
})
|
|
194
|
-
})
|