@socketsecurity/cli 0.8.0 → 0.9.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/lib/commands/info/index.js +100 -28
- package/lib/commands/login/index.js +35 -1
- package/lib/commands/logout/index.js +2 -0
- package/lib/commands/report/create.js +0 -1
- package/lib/shadow/npm-injection.cjs +70 -50
- package/lib/shadow/tty-server.cjs +3 -2
- package/lib/utils/path-resolve.js +54 -75
- package/lib/utils/sdk.js +38 -6
- package/lib/utils/settings.js +1 -1
- package/package.json +2 -3
|
@@ -20,10 +20,13 @@ export const info = {
|
|
|
20
20
|
const name = parentName + ' info'
|
|
21
21
|
|
|
22
22
|
const input = setupCommand(name, info.description, argv, importMeta)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (input) {
|
|
24
|
+
const spinnerText = input.pkgVersion === 'latest' ? `Looking up data for the latest version of ${input.pkgName}\n` : `Looking up data for version ${input.pkgVersion} of ${input.pkgName}\n`
|
|
25
|
+
const spinner = ora(spinnerText).start()
|
|
26
|
+
const packageData = await fetchPackageData(input.pkgName, input.pkgVersion, input, spinner)
|
|
27
|
+
if (packageData) {
|
|
28
|
+
formatPackageDataOutput(packageData, { name, ...input }, spinner)
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
}
|
|
@@ -90,16 +93,8 @@ function setupCommand (name, description, argv, importMeta) {
|
|
|
90
93
|
|
|
91
94
|
const versionSeparator = rawPkgName.lastIndexOf('@')
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const pkgName = rawPkgName.slice(0, versionSeparator)
|
|
98
|
-
const pkgVersion = rawPkgName.slice(versionSeparator + 1)
|
|
99
|
-
|
|
100
|
-
if (!pkgVersion) {
|
|
101
|
-
throw new InputError('Need to specify a version, like eg: webtorrent@1.0.0')
|
|
102
|
-
}
|
|
96
|
+
const pkgName = versionSeparator < 1 ? rawPkgName : rawPkgName.slice(0, versionSeparator)
|
|
97
|
+
const pkgVersion = versionSeparator < 1 ? 'latest' : rawPkgName.slice(versionSeparator + 1)
|
|
103
98
|
|
|
104
99
|
return {
|
|
105
100
|
includeAllIssues,
|
|
@@ -115,53 +110,78 @@ function setupCommand (name, description, argv, importMeta) {
|
|
|
115
110
|
* @typedef PackageData
|
|
116
111
|
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data
|
|
117
112
|
* @property {Record<import('../../utils/format-issues').SocketIssue['severity'], number>} severityCount
|
|
113
|
+
* @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getScoreByNPMPackage'>["data"]} score
|
|
118
114
|
*/
|
|
119
115
|
|
|
120
116
|
/**
|
|
121
117
|
* @param {string} pkgName
|
|
122
118
|
* @param {string} pkgVersion
|
|
123
|
-
* @param {Pick<CommandContext, 'includeAllIssues'
|
|
119
|
+
* @param {Pick<CommandContext, 'includeAllIssues'>} context
|
|
120
|
+
* @param {import('ora').Ora} spinner
|
|
124
121
|
* @returns {Promise<void|PackageData>}
|
|
125
122
|
*/
|
|
126
|
-
async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues,
|
|
123
|
+
async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues }, spinner) {
|
|
127
124
|
const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
|
|
128
|
-
const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start()
|
|
129
125
|
const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package')
|
|
126
|
+
const scoreResult = await handleApiCall(socketSdk.getScoreByNPMPackage(pkgName, pkgVersion), 'looking up package score')
|
|
130
127
|
|
|
131
128
|
if (result.success === false) {
|
|
132
129
|
return handleUnsuccessfulApiResponse('getIssuesByNPMPackage', result, spinner)
|
|
133
130
|
}
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
if (scoreResult.success === false) {
|
|
133
|
+
return handleUnsuccessfulApiResponse('getScoreByNPMPackage', scoreResult, spinner)
|
|
134
|
+
}
|
|
136
135
|
|
|
136
|
+
// Conclude the status of the API call
|
|
137
137
|
const severityCount = getSeverityCount(result.data, includeAllIssues ? undefined : 'high')
|
|
138
138
|
|
|
139
|
-
if (objectSome(severityCount)) {
|
|
140
|
-
const issueSummary = formatSeverityCount(severityCount)
|
|
141
|
-
spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
|
|
142
|
-
} else {
|
|
143
|
-
spinner.succeed('Package has no issues')
|
|
144
|
-
}
|
|
145
|
-
|
|
146
139
|
return {
|
|
147
140
|
data: result.data,
|
|
148
141
|
severityCount,
|
|
142
|
+
score: scoreResult.data
|
|
149
143
|
}
|
|
150
144
|
}
|
|
151
145
|
|
|
152
146
|
/**
|
|
153
147
|
* @param {PackageData} packageData
|
|
154
148
|
* @param {{ name: string } & CommandContext} context
|
|
149
|
+
* @param {import('ora').Ora} spinner
|
|
155
150
|
* @returns {void}
|
|
156
151
|
*/
|
|
157
|
-
function formatPackageDataOutput ({ data, severityCount }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }) {
|
|
152
|
+
function formatPackageDataOutput ({ data, severityCount, score }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }, spinner) {
|
|
158
153
|
if (outputJson) {
|
|
159
154
|
console.log(JSON.stringify(data, undefined, 2))
|
|
160
155
|
} else {
|
|
156
|
+
console.log('\nPackage report card:')
|
|
157
|
+
const scoreResult = {
|
|
158
|
+
'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100),
|
|
159
|
+
'Maintenance': Math.floor(score.maintenance.score * 100),
|
|
160
|
+
'Quality': Math.floor(score.quality.score * 100),
|
|
161
|
+
'Vulnerabilities': Math.floor(score.vulnerability.score * 100),
|
|
162
|
+
'License': Math.floor(score.license.score * 100)
|
|
163
|
+
}
|
|
164
|
+
Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`))
|
|
165
|
+
|
|
166
|
+
// Package issues list
|
|
167
|
+
if (objectSome(severityCount)) {
|
|
168
|
+
const issueSummary = formatSeverityCount(severityCount)
|
|
169
|
+
console.log('\n')
|
|
170
|
+
spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
|
|
171
|
+
formatPackageIssuesDetails(data, outputMarkdown)
|
|
172
|
+
} else {
|
|
173
|
+
console.log('\n')
|
|
174
|
+
spinner.succeed('Package has no issues')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Link to issues list
|
|
161
178
|
const format = new ChalkOrMarkdown(!!outputMarkdown)
|
|
162
179
|
const url = `https://socket.dev/npm/package/${pkgName}/overview/${pkgVersion}`
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
if (pkgVersion === 'latest') {
|
|
181
|
+
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName}`, url, { fallbackToUrl: true }))
|
|
182
|
+
} else {
|
|
183
|
+
console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true }))
|
|
184
|
+
}
|
|
165
185
|
if (!outputMarkdown) {
|
|
166
186
|
console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
|
|
167
187
|
}
|
|
@@ -171,3 +191,55 @@ async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict
|
|
|
171
191
|
process.exit(1)
|
|
172
192
|
}
|
|
173
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} packageData
|
|
197
|
+
* @param {boolean} outputMarkdown
|
|
198
|
+
* @returns {void[]}
|
|
199
|
+
*/
|
|
200
|
+
function formatPackageIssuesDetails (packageData, outputMarkdown) {
|
|
201
|
+
const issueDetails = packageData.filter(d => d.value?.severity === 'high' || d.value?.severity === 'critical')
|
|
202
|
+
|
|
203
|
+
const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: {count: Number, label: string | undefined} }} */ acc, issue) => {
|
|
204
|
+
const { type } = issue
|
|
205
|
+
if (type) {
|
|
206
|
+
if (!acc[type]) {
|
|
207
|
+
acc[type] = {
|
|
208
|
+
label: issue.value?.label,
|
|
209
|
+
count: 1
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
// @ts-ignore
|
|
213
|
+
acc[type].count += 1
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return acc
|
|
217
|
+
}, {})
|
|
218
|
+
|
|
219
|
+
const format = new ChalkOrMarkdown(!!outputMarkdown)
|
|
220
|
+
return Object.keys(uniqueIssues).map(issue => {
|
|
221
|
+
const issueWithLink = format.hyperlink(`${uniqueIssues[issue]?.label}`, `https://socket.dev/npm/issue/${issue}`, { fallbackToUrl: true })
|
|
222
|
+
if (uniqueIssues[issue]?.count === 1) {
|
|
223
|
+
return console.log(`- ${issueWithLink}`)
|
|
224
|
+
}
|
|
225
|
+
return console.log(`- ${issueWithLink}: ${uniqueIssues[issue]?.count}`)
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {number} score
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
function formatScore (score) {
|
|
234
|
+
const error = chalk.hex('#de7c7b')
|
|
235
|
+
const warning = chalk.hex('#e59361')
|
|
236
|
+
const success = chalk.hex('#a4cb9d')
|
|
237
|
+
|
|
238
|
+
if (score > 80) {
|
|
239
|
+
return `${success(score)}`
|
|
240
|
+
} else if (score < 80 && score > 60) {
|
|
241
|
+
return `${warning(score)}`
|
|
242
|
+
} else {
|
|
243
|
+
return `${error(score)}`
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -5,6 +5,8 @@ import prompts from 'prompts'
|
|
|
5
5
|
import terminalLink from 'terminal-link'
|
|
6
6
|
|
|
7
7
|
import { AuthError, InputError } from '../../utils/errors.js'
|
|
8
|
+
import { prepareFlags } from '../../utils/flags.js'
|
|
9
|
+
import { printFlagList } from '../../utils/formatting.js'
|
|
8
10
|
import { FREE_API_KEY, setupSdk } from '../../utils/sdk.js'
|
|
9
11
|
import { getSetting, updateSetting } from '../../utils/settings.js'
|
|
10
12
|
|
|
@@ -14,6 +16,16 @@ const description = 'Socket API login'
|
|
|
14
16
|
export const login = {
|
|
15
17
|
description,
|
|
16
18
|
run: async (argv, importMeta, { parentName }) => {
|
|
19
|
+
const flags = prepareFlags({
|
|
20
|
+
apiBaseUrl: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'API server to connect to for login',
|
|
23
|
+
},
|
|
24
|
+
apiProxy: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Proxy to use when making connection to API server'
|
|
27
|
+
}
|
|
28
|
+
})
|
|
17
29
|
const name = parentName + ' login'
|
|
18
30
|
const cli = meow(`
|
|
19
31
|
Usage
|
|
@@ -21,12 +33,19 @@ export const login = {
|
|
|
21
33
|
|
|
22
34
|
Logs into the Socket API by prompting for an API key
|
|
23
35
|
|
|
36
|
+
Options
|
|
37
|
+
${printFlagList({
|
|
38
|
+
'api-base-url': flags.apiBaseUrl.description,
|
|
39
|
+
'api-proxy': flags.apiProxy.description
|
|
40
|
+
}, 8)}
|
|
41
|
+
|
|
24
42
|
Examples
|
|
25
43
|
$ ${name}
|
|
26
44
|
`, {
|
|
27
45
|
argv,
|
|
28
46
|
description,
|
|
29
47
|
importMeta,
|
|
48
|
+
flags
|
|
30
49
|
})
|
|
31
50
|
|
|
32
51
|
/**
|
|
@@ -58,13 +77,27 @@ export const login = {
|
|
|
58
77
|
|
|
59
78
|
const apiKey = result.apiKey || FREE_API_KEY
|
|
60
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @type {string | null | undefined}
|
|
82
|
+
*/
|
|
83
|
+
let apiBaseUrl = cli.flags.apiBaseUrl
|
|
84
|
+
apiBaseUrl ??= getSetting('apiBaseUrl') ??
|
|
85
|
+
undefined
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @type {string | null | undefined}
|
|
89
|
+
*/
|
|
90
|
+
let apiProxy = cli.flags.apiProxy
|
|
91
|
+
apiProxy ??= getSetting('apiProxy') ??
|
|
92
|
+
undefined
|
|
93
|
+
|
|
61
94
|
const spinner = ora('Verifying API key...').start()
|
|
62
95
|
|
|
63
96
|
/** @type {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrganizations'>['data']} */
|
|
64
97
|
let orgs
|
|
65
98
|
|
|
66
99
|
try {
|
|
67
|
-
const sdk = await setupSdk(apiKey)
|
|
100
|
+
const sdk = await setupSdk(apiKey, apiBaseUrl, apiProxy)
|
|
68
101
|
const result = await sdk.getOrganizations()
|
|
69
102
|
if (!result.success) throw new AuthError()
|
|
70
103
|
orgs = result.data
|
|
@@ -131,6 +164,7 @@ export const login = {
|
|
|
131
164
|
updateSetting('enforcedOrgs', enforcedOrgs)
|
|
132
165
|
const oldKey = getSetting('apiKey')
|
|
133
166
|
updateSetting('apiKey', apiKey)
|
|
167
|
+
updateSetting('apiBaseUrl', apiBaseUrl)
|
|
134
168
|
spinner.succeed(`API credentials ${oldKey ? 'updated' : 'set'}`)
|
|
135
169
|
}
|
|
136
170
|
}
|
|
@@ -179,7 +179,6 @@ async function setupCommand (name, description, argv, importMeta) {
|
|
|
179
179
|
}
|
|
180
180
|
})
|
|
181
181
|
|
|
182
|
-
// TODO: setupSdk(getDefaultKey() || FREE_API_KEY)
|
|
183
182
|
const socketSdk = await setupSdk()
|
|
184
183
|
const supportedFiles = await socketSdk.getReportSupportedFiles()
|
|
185
184
|
.then(res => {
|
|
@@ -40,34 +40,50 @@ try {
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
const pubTokenPromise = sdkPromise.then(({ getDefaultKey, FREE_API_KEY }) => getDefaultKey() || FREE_API_KEY)
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
const apiKeySettingsInit = sdkPromise.then(async ({ setupSdk }) => {
|
|
44
|
+
try {
|
|
45
|
+
const sdk = await setupSdk(await pubTokenPromise)
|
|
46
|
+
const orgResult = await sdk.getOrganizations()
|
|
47
|
+
if (!orgResult.success) {
|
|
48
|
+
throw new Error('Failed to fetch Socket organization info: ' + orgResult.error.message)
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @type {(Exclude<typeof orgResult.data.organizations[string], undefined>)[]}
|
|
52
|
+
*/
|
|
53
|
+
const orgs = []
|
|
54
|
+
for (const org of Object.values(orgResult.data.organizations)) {
|
|
55
|
+
if (org) {
|
|
56
|
+
orgs.push(org)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const result = await sdk.postSettings(orgs.map(org => {
|
|
60
|
+
return {
|
|
61
|
+
organization: org.id
|
|
62
|
+
}
|
|
63
|
+
}))
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
throw new Error('Failed to fetch API key settings: ' + result.error.message)
|
|
56
66
|
}
|
|
57
|
-
}
|
|
58
|
-
const result = await sdk.postSettings(orgs.map(org => {
|
|
59
67
|
return {
|
|
60
|
-
|
|
68
|
+
orgs,
|
|
69
|
+
settings: result.data
|
|
61
70
|
}
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e && typeof e === 'object' && 'cause' in e) {
|
|
73
|
+
const cause = e.cause
|
|
74
|
+
if (isErrnoException(cause)) {
|
|
75
|
+
if (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED') {
|
|
76
|
+
throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
|
|
77
|
+
cause: e
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw e
|
|
69
83
|
}
|
|
70
84
|
})
|
|
85
|
+
// mark apiKeySettingsInit as handled
|
|
86
|
+
apiKeySettingsInit.catch(() => {})
|
|
71
87
|
|
|
72
88
|
/**
|
|
73
89
|
*
|
|
@@ -78,42 +94,43 @@ async function findSocketYML () {
|
|
|
78
94
|
const fs = require('fs/promises')
|
|
79
95
|
while (dir !== prevDir) {
|
|
80
96
|
const ymlPath = path.join(dir, 'socket.yml')
|
|
97
|
+
const yml = fs.readFile(ymlPath, 'utf-8')
|
|
81
98
|
// mark as handled
|
|
82
|
-
|
|
99
|
+
yml.catch(() => {})
|
|
83
100
|
const yamlPath = path.join(dir, 'socket.yaml')
|
|
101
|
+
const yaml = fs.readFile(yamlPath, 'utf-8')
|
|
84
102
|
// mark as handled
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
parsed: config.parseSocketConfig(txt)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} catch (e) {
|
|
103
|
+
yaml.catch(() => {})
|
|
104
|
+
/**
|
|
105
|
+
* @param {unknown} e
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
function checkFileFoundError (e) {
|
|
95
109
|
if (isErrnoException(e)) {
|
|
96
110
|
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
|
|
97
111
|
throw e
|
|
98
112
|
}
|
|
99
|
-
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return {
|
|
119
|
+
path: ymlPath,
|
|
120
|
+
parsed: config.parseSocketConfig(await yml)
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (checkFileFoundError(e)) {
|
|
100
124
|
throw new Error('Found file but was unable to parse ' + ymlPath)
|
|
101
125
|
}
|
|
102
126
|
}
|
|
103
127
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
path: yamlPath,
|
|
108
|
-
parsed: config.parseSocketConfig(txt)
|
|
109
|
-
}
|
|
128
|
+
return {
|
|
129
|
+
path: ymlPath,
|
|
130
|
+
parsed: config.parseSocketConfig(await yaml)
|
|
110
131
|
}
|
|
111
132
|
} catch (e) {
|
|
112
|
-
if (
|
|
113
|
-
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
|
|
114
|
-
throw e
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
133
|
+
if (checkFileFoundError(e)) {
|
|
117
134
|
throw new Error('Found file but was unable to parse ' + yamlPath)
|
|
118
135
|
}
|
|
119
136
|
}
|
|
@@ -124,11 +141,12 @@ async function findSocketYML () {
|
|
|
124
141
|
}
|
|
125
142
|
|
|
126
143
|
/**
|
|
127
|
-
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']
|
|
144
|
+
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']> | undefined>}
|
|
128
145
|
*/
|
|
129
|
-
const
|
|
146
|
+
const uxLookupInit = settingsPromise.then(async ({ getSetting }) => {
|
|
130
147
|
const enforcedOrgs = getSetting('enforcedOrgs') ?? []
|
|
131
|
-
const
|
|
148
|
+
const remoteSettings = await apiKeySettingsInit
|
|
149
|
+
const { orgs, settings } = remoteSettings
|
|
132
150
|
|
|
133
151
|
// remove any organizations not being enforced
|
|
134
152
|
for (const [i, org] of orgs.entries()) {
|
|
@@ -152,6 +170,8 @@ const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
|
|
|
152
170
|
}
|
|
153
171
|
return createIssueUXLookup(settings)
|
|
154
172
|
})
|
|
173
|
+
// mark uxLookupInit as handled
|
|
174
|
+
uxLookupInit.catch(() => {})
|
|
155
175
|
|
|
156
176
|
// shadow `npm` and `npx` to mitigate subshells
|
|
157
177
|
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
|
|
@@ -506,7 +526,7 @@ async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _i
|
|
|
506
526
|
const pkgDatas = []
|
|
507
527
|
try {
|
|
508
528
|
// TODO: determine org based on cwd, pass in
|
|
509
|
-
const uxLookup = await
|
|
529
|
+
const uxLookup = await uxLookupInit
|
|
510
530
|
|
|
511
531
|
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
|
|
512
532
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const { PassThrough } = require('stream')
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
const ipc_version = require('../../package.json').version
|
|
5
|
+
const { isErrnoException } = require('../utils/type-helpers.cjs')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {import('stream').Readable} Readable
|
|
@@ -22,7 +23,7 @@ module.exports = async function createTTYServer (colorLevel, isInteractive, npml
|
|
|
22
23
|
* @type {import('readline')}
|
|
23
24
|
*/
|
|
24
25
|
let readline
|
|
25
|
-
const isSTDINInteractive =
|
|
26
|
+
const isSTDINInteractive = isInteractive
|
|
26
27
|
if (!isSTDINInteractive && TTY_IPC) {
|
|
27
28
|
return {
|
|
28
29
|
async captureTTY (mutexFn) {
|
|
@@ -5,7 +5,6 @@ import { globby } from 'globby'
|
|
|
5
5
|
import ignore from 'ignore'
|
|
6
6
|
// @ts-ignore This package provides no types
|
|
7
7
|
import { directories } from 'ignore-by-default'
|
|
8
|
-
import micromatch from 'micromatch'
|
|
9
8
|
import { ErrorWithCause } from 'pony-cause'
|
|
10
9
|
|
|
11
10
|
import { InputError } from './errors.js'
|
|
@@ -94,85 +93,65 @@ export async function mapGlobResultToFiles (entries, supportedFiles) {
|
|
|
94
93
|
* @throws {InputError}
|
|
95
94
|
*/
|
|
96
95
|
export async function mapGlobEntryToFiles (entry, supportedFiles) {
|
|
97
|
-
/** @type {string|undefined} */
|
|
98
|
-
let pkgJSFile
|
|
99
|
-
/** @type {string[]} */
|
|
100
|
-
let jsLockFiles = []
|
|
101
|
-
/** @type {string[]} */
|
|
102
|
-
let pyFiles = []
|
|
103
|
-
/** @type {string|undefined} */
|
|
104
|
-
let pkgGoFile
|
|
105
|
-
/** @type {string[]} */
|
|
106
|
-
let goExtraFiles = []
|
|
107
|
-
|
|
108
96
|
const jsSupported = supportedFiles['npm'] || {}
|
|
109
|
-
const jsLockFilePatterns = Object.
|
|
110
|
-
.filter(key => key !== 'packagejson')
|
|
111
|
-
.map(
|
|
97
|
+
const jsLockFilePatterns = Object.values(jsSupported)
|
|
98
|
+
// .filter(key => key !== 'packagejson')
|
|
99
|
+
.map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
|
|
112
100
|
|
|
113
101
|
const pyFilePatterns = Object.values(supportedFiles['pypi'] || {})
|
|
114
|
-
.map(p => /** @type {{ pattern: string }} */ (p).pattern)
|
|
102
|
+
.map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
|
|
115
103
|
|
|
116
104
|
const goSupported = supportedFiles['go'] || {}
|
|
117
|
-
const goSupplementalPatterns = Object.
|
|
118
|
-
.filter(key => key !== 'gomod')
|
|
119
|
-
.map(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
goExtraFiles = await globby(goSupplementalPatterns, {
|
|
168
|
-
...BASE_GLOBBY_OPTS,
|
|
169
|
-
cwd: pkgDir
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return [...jsLockFiles, ...pyFiles, ...goExtraFiles]
|
|
174
|
-
.concat(pkgJSFile ? [pkgJSFile] : [])
|
|
175
|
-
.concat(pkgGoFile ? [pkgGoFile] : [])
|
|
105
|
+
const goSupplementalPatterns = Object.values(goSupported)
|
|
106
|
+
// .filter(key => key !== 'gomod')
|
|
107
|
+
.map(p => `**/${/** @type {{ pattern: string }} */ (p).pattern}`)
|
|
108
|
+
|
|
109
|
+
const files = await globby([
|
|
110
|
+
...jsLockFilePatterns,
|
|
111
|
+
...pyFilePatterns,
|
|
112
|
+
...goSupplementalPatterns
|
|
113
|
+
], {
|
|
114
|
+
...BASE_GLOBBY_OPTS,
|
|
115
|
+
onlyFiles: true,
|
|
116
|
+
cwd: path.resolve((await stat(entry)).isDirectory() ? entry : path.dirname(entry))
|
|
117
|
+
})
|
|
118
|
+
return files
|
|
119
|
+
|
|
120
|
+
// if (entry.endsWith('/')) {
|
|
121
|
+
// // If the match is a folder and that folder contains a package.json file, then include it
|
|
122
|
+
// const jsPkg = path.resolve(entry, 'package.json')
|
|
123
|
+
// if (await fileExists(jsPkg)) pkgJSFile = jsPkg
|
|
124
|
+
|
|
125
|
+
// const goPkg = path.resolve(entry, 'go.mod')
|
|
126
|
+
// if (await fileExists(goPkg)) pkgGoFile = goPkg
|
|
127
|
+
|
|
128
|
+
// pyFiles = await globby(pyFilePatterns, {
|
|
129
|
+
// ...BASE_GLOBBY_OPTS,
|
|
130
|
+
// cwd: entry
|
|
131
|
+
// })
|
|
132
|
+
// } else {
|
|
133
|
+
// const entryFile = path.basename(entry)
|
|
134
|
+
|
|
135
|
+
// if (entryFile === 'package.json') {
|
|
136
|
+
// // If the match is a package.json file, then include it
|
|
137
|
+
// pkgJSFile = entry
|
|
138
|
+
// } else if (micromatch.isMatch(entryFile, jsLockFilePatterns)) {
|
|
139
|
+
// jsLockFiles = [entry]
|
|
140
|
+
// pkgJSFile = path.resolve(path.dirname(entry), 'package.json')
|
|
141
|
+
// if (!(await fileExists(pkgJSFile))) return []
|
|
142
|
+
// } else if (entryFile === 'go.mod') {
|
|
143
|
+
// pkgGoFile = entry
|
|
144
|
+
// } else if (micromatch.isMatch(entryFile, goSupplementalPatterns)) {
|
|
145
|
+
// goExtraFiles = [entry]
|
|
146
|
+
// pkgGoFile = path.resolve(path.dirname(entry), 'go.mod')
|
|
147
|
+
// } else if (micromatch.isMatch(entryFile, pyFilePatterns)) {
|
|
148
|
+
// pyFiles = [entry]
|
|
149
|
+
// }
|
|
150
|
+
// }
|
|
151
|
+
|
|
152
|
+
// return [...jsLockFiles, ...pyFiles, ...goExtraFiles]
|
|
153
|
+
// .concat(pkgJSFile ? [pkgJSFile] : [])
|
|
154
|
+
// .concat(pkgGoFile ? [pkgGoFile] : [])
|
|
176
155
|
}
|
|
177
156
|
|
|
178
157
|
/**
|
package/lib/utils/sdk.js
CHANGED
|
@@ -20,15 +20,47 @@ let defaultKey
|
|
|
20
20
|
|
|
21
21
|
/** @returns {string | undefined} */
|
|
22
22
|
export function getDefaultKey () {
|
|
23
|
-
defaultKey =
|
|
23
|
+
defaultKey = process.env['SOCKET_SECURITY_API_KEY'] || getSetting('apiKey') || defaultKey
|
|
24
24
|
return defaultKey
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The API server that should be used for operations
|
|
29
|
+
*
|
|
30
|
+
* @type {string | undefined}
|
|
31
|
+
*/
|
|
32
|
+
let defaultAPIBaseUrl
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @returns {string | undefined}
|
|
36
|
+
*/
|
|
37
|
+
export function getDefaultAPIBaseUrl () {
|
|
38
|
+
defaultAPIBaseUrl = process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl') || undefined
|
|
39
|
+
return defaultAPIBaseUrl
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The API server that should be used for operations
|
|
44
|
+
*
|
|
45
|
+
* @type {string | undefined}
|
|
46
|
+
*/
|
|
47
|
+
let defaultApiProxy
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @returns {string | undefined}
|
|
51
|
+
*/
|
|
52
|
+
export function getDefaultHTTPProxy () {
|
|
53
|
+
defaultApiProxy = process.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy') || undefined
|
|
54
|
+
return defaultApiProxy
|
|
55
|
+
}
|
|
56
|
+
|
|
27
57
|
/**
|
|
28
58
|
* @param {string} [apiKey]
|
|
59
|
+
* @param {string} [apiBaseUrl]
|
|
60
|
+
* @param {string} [proxy]
|
|
29
61
|
* @returns {Promise<import('@socketsecurity/sdk').SocketSdk>}
|
|
30
62
|
*/
|
|
31
|
-
export async function setupSdk (apiKey = getDefaultKey()) {
|
|
63
|
+
export async function setupSdk (apiKey = getDefaultKey(), apiBaseUrl = getDefaultAPIBaseUrl(), proxy = getDefaultHTTPProxy()) {
|
|
32
64
|
if (apiKey == null && isInteractive()) {
|
|
33
65
|
/**
|
|
34
66
|
* @type {{ apiKey: string }}
|
|
@@ -49,11 +81,11 @@ export async function setupSdk (apiKey = getDefaultKey()) {
|
|
|
49
81
|
/** @type {import('@socketsecurity/sdk').SocketSdkOptions["agent"]} */
|
|
50
82
|
let agent
|
|
51
83
|
|
|
52
|
-
if (
|
|
84
|
+
if (proxy) {
|
|
53
85
|
const { HttpProxyAgent, HttpsProxyAgent } = await import('hpagent')
|
|
54
86
|
agent = {
|
|
55
|
-
http: new HttpProxyAgent({ proxy
|
|
56
|
-
https: new HttpsProxyAgent({ proxy
|
|
87
|
+
http: new HttpProxyAgent({ proxy }),
|
|
88
|
+
https: new HttpsProxyAgent({ proxy }),
|
|
57
89
|
}
|
|
58
90
|
}
|
|
59
91
|
const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json')
|
|
@@ -62,7 +94,7 @@ export async function setupSdk (apiKey = getDefaultKey()) {
|
|
|
62
94
|
/** @type {import('@socketsecurity/sdk').SocketSdkOptions} */
|
|
63
95
|
const sdkOptions = {
|
|
64
96
|
agent,
|
|
65
|
-
baseUrl:
|
|
97
|
+
baseUrl: apiBaseUrl,
|
|
66
98
|
userAgent: createUserAgentFromPkgJson(JSON.parse(packageJson))
|
|
67
99
|
}
|
|
68
100
|
|
package/lib/utils/settings.js
CHANGED
|
@@ -23,7 +23,7 @@ const settingsPath = path.join(dataHome, 'socket', 'settings')
|
|
|
23
23
|
* @typedef {Record<string, boolean | {action: 'error' | 'warn' | 'ignore' | 'defer'}>} IssueRules
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
/** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null}} */
|
|
26
|
+
/** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null, apiBaseUrl?: string | null, apiProxy?: string | null}} */
|
|
27
27
|
let settings = {}
|
|
28
28
|
|
|
29
29
|
if (fs.existsSync(settingsPath)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CLI tool for Socket.dev",
|
|
5
5
|
"homepage": "http://github.com/SocketDev/socket-cli-js",
|
|
6
6
|
"repository": {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@apideck/better-ajv-errors": "^0.3.6",
|
|
84
84
|
"@socketsecurity/config": "^2.0.0",
|
|
85
|
-
"@socketsecurity/sdk": "^0.7.
|
|
85
|
+
"@socketsecurity/sdk": "^0.7.3",
|
|
86
86
|
"chalk": "^5.1.2",
|
|
87
87
|
"globby": "^13.1.3",
|
|
88
88
|
"hpagent": "^1.2.0",
|
|
@@ -91,7 +91,6 @@
|
|
|
91
91
|
"is-interactive": "^2.0.0",
|
|
92
92
|
"is-unicode-supported": "^1.3.0",
|
|
93
93
|
"meow": "^12.0.1",
|
|
94
|
-
"micromatch": "^4.0.5",
|
|
95
94
|
"ora": "^6.1.2",
|
|
96
95
|
"pony-cause": "^2.1.8",
|
|
97
96
|
"prompts": "^2.4.2",
|